Commit 707fbf63 by tady

Merge pull request #31 from tadyjp/wip/140301_tag_manager

[WIP] タグ編集機能
parents 24daa358 28f34221
...@@ -116,3 +116,9 @@ gem 'settingslogic' ...@@ -116,3 +116,9 @@ gem 'settingslogic'
# Check mail format # Check mail format
gem 'validates_email_format_of' gem 'validates_email_format_of'
# Presentaion layer
gem 'draper', '~> 1.3'
# ActiveRecord versioning
gem 'paper_trail', '~> 3.0.0'
...@@ -39,8 +39,8 @@ GEM ...@@ -39,8 +39,8 @@ GEM
activerecord (>= 3.0.0) activerecord (>= 3.0.0)
arel (4.0.2) arel (4.0.2)
ast (1.1.0) ast (1.1.0)
atomic (1.1.14) atomic (1.1.15)
bcrypt (3.1.6) bcrypt (3.1.7)
bcrypt-ruby (3.1.5) bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3) bcrypt (>= 3.1.3)
better_errors (1.1.0) better_errors (1.1.0)
...@@ -103,12 +103,17 @@ GEM ...@@ -103,12 +103,17 @@ GEM
docile (1.1.3) docile (1.1.3)
domain_name (0.5.16) domain_name (0.5.16)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
draper (1.3.0)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
request_store (~> 1.0.3)
erubis (2.7.0) erubis (2.7.0)
eventmachine (1.0.3) eventmachine (1.0.3)
execjs (2.0.2) execjs (2.0.2)
factory_girl (4.4.0) factory_girl (4.4.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.4.0) factory_girl_rails (4.4.1)
factory_girl (~> 4.4.0) factory_girl (~> 4.4.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faraday (0.9.0) faraday (0.9.0)
...@@ -118,9 +123,9 @@ GEM ...@@ -118,9 +123,9 @@ GEM
github-markdown (0.6.4) github-markdown (0.6.4)
gmail_xoauth (0.4.1) gmail_xoauth (0.4.1)
oauth (>= 0.3.6) oauth (>= 0.3.6)
guard (2.4.0) guard (2.5.1)
formatador (>= 0.2.4) formatador (>= 0.2.4)
listen (~> 2.1) listen (~> 2.6)
lumberjack (~> 1.0) lumberjack (~> 1.0)
pry (>= 0.9.12) pry (>= 0.9.12)
thor (>= 0.18.1) thor (>= 0.18.1)
...@@ -151,7 +156,7 @@ GEM ...@@ -151,7 +156,7 @@ GEM
launchy (2.4.2) launchy (2.4.2)
addressable (~> 2.3) addressable (~> 2.3)
libv8 (3.16.14.3) libv8 (3.16.14.3)
listen (2.6.0) listen (2.6.2)
celluloid (>= 0.15.2) celluloid (>= 0.15.2)
celluloid-io (>= 0.15.0) celluloid-io (>= 0.15.0)
rb-fsevent (>= 0.9.3) rb-fsevent (>= 0.9.3)
...@@ -205,7 +210,10 @@ GEM ...@@ -205,7 +210,10 @@ GEM
oauth2 (~> 0.9.3) oauth2 (~> 0.9.3)
omniauth (~> 1.2) omniauth (~> 1.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
parser (2.1.4) paper_trail (3.0.0)
activerecord (>= 3.0, < 5.0)
activesupport (>= 3.0, < 5.0)
parser (2.1.5)
ast (~> 1.1) ast (~> 1.1)
slop (~> 3.4, >= 3.4.5) slop (~> 3.4, >= 3.4.5)
poltergeist (1.5.0) poltergeist (1.5.0)
...@@ -250,13 +258,14 @@ GEM ...@@ -250,13 +258,14 @@ GEM
rdoc (4.1.1) rdoc (4.1.1)
json (~> 1.4) json (~> 1.4)
ref (1.0.5) ref (1.0.5)
request_store (1.0.5)
rest-client (1.6.7) rest-client (1.6.7)
mime-types (>= 1.16) mime-types (>= 1.16)
rspec (2.14.1) rspec (2.14.1)
rspec-core (~> 2.14.0) rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0) rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0) rspec-mocks (~> 2.14.0)
rspec-core (2.14.7) rspec-core (2.14.8)
rspec-expectations (2.14.5) rspec-expectations (2.14.5)
diff-lcs (>= 1.1.3, < 2.0) diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.6) rspec-mocks (2.14.6)
...@@ -311,8 +320,8 @@ GEM ...@@ -311,8 +320,8 @@ GEM
eventmachine (>= 1.0.0) eventmachine (>= 1.0.0)
rack (>= 1.0.0) rack (>= 1.0.0)
thor (0.18.1) thor (0.18.1)
thread_safe (0.1.3) thread_safe (0.2.0)
atomic atomic (>= 1.1.7, < 2)
tilt (1.4.1) tilt (1.4.1)
timers (1.1.0) timers (1.1.0)
tins (1.0.0) tins (1.0.0)
...@@ -352,6 +361,7 @@ DEPENDENCIES ...@@ -352,6 +361,7 @@ DEPENDENCIES
coveralls coveralls
database_rewinder database_rewinder
devise devise
draper (~> 1.3)
factory_girl_rails factory_girl_rails
faraday faraday
github-markdown github-markdown
...@@ -365,6 +375,7 @@ DEPENDENCIES ...@@ -365,6 +375,7 @@ DEPENDENCIES
mysql2 mysql2
nokogiri nokogiri
omniauth-google-oauth2 omniauth-google-oauth2
paper_trail (~> 3.0.0)
poltergeist poltergeist
premailer premailer
pry-rails pry-rails
......
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
...@@ -17,3 +17,5 @@ ...@@ -17,3 +17,5 @@
//= require_tree ./lib //= require_tree ./lib
//= require_tree . //= require_tree .
_.mixin(_.string.exports());
$ -> $ ->
# search form animation # 上部の検索フォームのアニメーション
$('#app-search-form input') $('#app-search-form input')
.on 'focus', -> .on 'focus', ->
$(this).parents('#app-search-form').animate({width: '400px'}) $(this).parents('#app-search-form').animate({width: '400px'})
.on 'blur', -> .on 'blur', ->
$(this).parents('#app-search-form').animate({width: '200px'}) $(this).parents('#app-search-form').animate({width: '200px'})
# コードハイライト
# TODO
prettyPrint() prettyPrint()
# set debug method to window
# usage:
# debug()(something)
# And url must include '#debug' hash.
window.debug = -> window.debug = ->
if console? && console.debug? && location.hash is '#debug' if console? && console.debug? && location.hash is '#debug'
return console.log.bind(console) return console.log.bind(console)
......
# Desc
# ページから離脱するときに確認画面を出す
# `.js-disable-confirm-unload`クラスのついた要素をクリックした時に無効にする
# Usage:
# $.setConfirmUnload()
$.extend
setConfirmUnload: ->
confirmUnload = ->
return 'このページを離れますか?'
$(window).on('beforeunload', confirmUnload)
$('.js-disable-confirm-unload').on 'click', ->
$(window).off('beforeunload', confirmUnload)
return @
# TODO:
# mod-mdEditorがページ内に複数あった場合の処理
$.fn.extend
mod_mdEditor: (options) ->
settings =
# preview api url
end_point: ''
settings = $.extend settings, options
return @each ()->
$root = $(@)
# Automaticaly change textarea height.
$root.find('.mod-mdEditor-body').autosize();
# disable tab key
$root.find('.mod-mdEditor-body').on 'keydown', (e) ->
$this = $(@)
keyCode = e.keyCode || e.which
# tab key
if keyCode is 9
e.preventDefault()
start = $this.get(0).selectionStart
end = $this.get(0).selectionEnd
# set textarea value to: text before caret + tab + text after caret
$this.val($this.val().substring(0, start) +
'\t' +
$this.val().substring(end))
# put caret at right position again
$this.get(0).selectionStart =
$this.get(0).selectionEnd = start + 1
# enter key
else if keyCode is 13
val = $this.val()
start = $this.get(0).selectionStart
bl = val.lastIndexOf("\n", start-1)
line = val.substring(bl, start)
lm = line.match(/^\s+/)
ns = if lm? then lm[0].length - 1 else 0
nv = val.substring(0, start) + "\n"
_(ns).times ->
nv += "\t"
$this.val(nv + val.substring(start))
$this.get(0).selectionStart =
$this.get(0).selectionEnd = start + ns + 1
e.preventDefault()
# タグを選択可能に
$root.find('.mod-mdEditor-tags').select2 {
tags: window.RV.AllTags
}
# Previewを生成
generatePreview = ->
$.post(settings.end_point, {
'text': $root.find('.mod-mdEditor-body').val()
'authenticity_token': $("meta[name='csrf-token']").attr('content')
})
.done (data) ->
$root.find('.mod-mdEditor-preview').html(data)
# TODO
prettyPrint()
$('.mod-mdEditor-body').on('keyup mouseup', generatePreview)
generatePreview()
# .mod-tag-tree-filterの入力文字列をもとにmod-tag-tree内のliをフィルタリング
$ ->
$('.mod-tag-tree').each ->
$root = $(this)
$root.find('.mod-tag-tree-filter').on 'change keyup', ->
$input = $(this)
if _.isEmpty($input.val())
$root.find('li').show()
return
$root.find('li a').each ->
$a = $(this)
# aタグに検索文字列が含まれれば表示
if _.str.include $a.text().toLowerCase(), $input.val().toLowerCase()
$a.parent('li').show()
else
$a.parent('li').hide()
if window.location.pathname.match /edit|new|fork/
$ ->
confirmUnload = ->
return 'このページを離れますか?'
$(window).on('beforeunload', confirmUnload)
$('#save_button').on 'click', ->
$(window).off('beforeunload', confirmUnload)
# Automaticaly change textarea height.
$('textarea.autosize').autosize();
# disable tab key
$('.disable-tab').on 'keydown', (e) ->
$this = $(this)
keyCode = e.keyCode || e.which
# tab key
if keyCode is 9
e.preventDefault()
start = $this.get(0).selectionStart
end = $this.get(0).selectionEnd
# set textarea value to: text before caret + tab + text after caret
$this.val($this.val().substring(0, start) +
'\t' +
$this.val().substring(end))
# put caret at right position again
$this.get(0).selectionStart =
$this.get(0).selectionEnd = start + 1
# enter key
else if keyCode is 13
val = $this.val()
start = $this.get(0).selectionStart
bl = val.lastIndexOf("\n", start-1)
line = val.substring(bl, start)
lm = line.match(/^\s+/)
ns = if lm? then lm[0].length - 1 else 0
nv = val.substring(0, start) + "\n"
_(ns).times ->
nv += "\t"
$this.val(nv + val.substring(start))
$this.get(0).selectionStart =
$this.get(0).selectionEnd = start + ns + 1
e.preventDefault()
# new post tags
$('#post_tags').select2 {
tags: window.RV.AllTags
}
# Preview post.
load_preview = ->
text = $('#post_body').val()
csrfToken = $("meta[name='csrf-token']").attr('content')
$.post('/posts/preview.api', {
'text': text
'authenticity_token': csrfToken
})
.done (data) ->
$('#post_preview').html(data)
prettyPrint()
$('#post_body').on('keyup mouseup', load_preview)
load_preview()
// Place all the styles related to the apis controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
...@@ -2,118 +2,20 @@ ...@@ -2,118 +2,20 @@
// They will automatically be included in application.css. // They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/ // You can use Sass (SCSS) here: http://sass-lang.com/
.app { .navbar-default .navbar-brand {
background-color: #428bca;
.navbar-default .navbar-brand { color: #fff;
background-color: #428bca; }
color: #fff;
}
.label a {
color: white;
}
/* .navbar-default a.btn, .navbar-default a.btn:hover{
padding: 10px 20px;
color: #fff;
margin-top: 6px;
}
*/
.box-text {
padding: 9px 14px;
margin-left: 0;
margin-right: 0;
background-color: #fff;
border-width: 1px;
border-color: #ddd;
border: 1px solid #e1e1e8;
border-radius: 4px 4px 0 0;
box-shadow: none;
}
.box-highlight {
padding: 9px 14px;
margin-bottom: 14px;
background-color: #f7f7f9;
border: 1px solid #e1e1e8;
border-radius: 4px;
}
.box-text + .box-highlight {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
#app-search-form {
width: 200px;
}
/* side tree view
-------------------------------------------------- */
/* ul {
padding-left: 1em;
}
*/
/* home#show
-------------------------------------------------- */
/* .text-box {
padding: 9px 14px;
border: 1px solid #e1e1e8;
border-radius: 4px;
}
.text-box.title {
background-color: #fff;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
font-weight: bold;
font-size: 12pt;
}
.text-box.meta {
background-color: #f7f7f9;
border-radius: 0;
.label {
display: inline-block;
}
}
.text-box.body {
background-color: #fff;
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
*/
/* posts#new
-------------------------------------------------- */
#post_title {
width: 95%;
height: 40px;
font-weight: bold;
font-size: 25px;
}
.input-group-addon {
label {
margin: 0;
}
}
#post_body { .navbar-default .navbar-brand,
width: 100%; .label a {
min-height: 400px; color: white;
font-size: 9pt; }
padding: 3px;
tab-size: 2;
}
#post_preview { #app-search-form {
min-height: 400px; width: 200px;
} }
.editor { .container-main{
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'Ricty', monospace; min-height: 400px;
}
} }
body[class^="posts-"], body {
body[class^="tags-"] {
padding-top: 70px; padding-top: 70px;
} }
/* Style for mod-mdEditor.
-------------------------------------------------- */
.mod-mdEditor-title {
width: 95%;
height: 40px;
font-weight: bold;
font-size: 25px;
}
// .input-group-addon {
// label {
// margin: 0;
// }
// }
.mod-mdEditor-body {
width: 100%;
min-height: 400px;
font-size: 9pt;
padding: 3px;
tab-size: 2;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'Ricty', monospace;
}
.mod-mdEditor-preview {
min-height: 400px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'Ricty', monospace;
}
ul.mod-tag-tree {
margin: 0;
padding: 0;
line-height: 1.5;
list-style: none;
a {
display: inline-block;
}
ul {
margin: 0;
padding: 0;
line-height: 1.5;
list-style: none;
margin-left: 1.5em;
}
li {
margin: 0 0 0 0.5em;
padding: 0;
border-left: 1px solid #999;
zoom: 1;
}
li:before {
margin-right: 0.5em;
border-bottom: 1px solid #999;
float: left;
width: 1em;
height: 0.75em;
overflow: hidden;
content: "";
}
li:last-child {
border: none;
}
li:last-child:before {
border-left: 1px solid #999;
}
.mod-tag-tree-filter {
}
}
class ApisController < ApplicationController
# TODO: not to use
include ApplicationHelper
def markdown_preview
# TODO: not to use
render text: h_application_format_markdown(params[:text] || '')
end
end
class HomeController < ApplicationController class HomeController < ApplicationController
skip_before_action :require_login skip_before_action :require_login
layout 'welcome'
def top def top
if user_signed_in? if user_signed_in?
......
...@@ -3,9 +3,7 @@ require 'nkf' ...@@ -3,9 +3,7 @@ require 'nkf'
class PostsController < ApplicationController class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy] before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :require_login before_action :require_login
layout 'app'
include ApplicationHelper
include RV::Mailer include RV::Mailer
# GET /posts # GET /posts
...@@ -18,10 +16,6 @@ class PostsController < ApplicationController ...@@ -18,10 +16,6 @@ class PostsController < ApplicationController
end end
end end
def preview
render text: h_application_format_markdown(params[:text])
end
# GET /posts/1 # GET /posts/1
# GET /posts/1.json # GET /posts/1.json
def show def show
......
class TagsController < ApplicationController class TagsController < ApplicationController
layout 'app' before_action :set_tag, only: [:show, :edit, :update, :destroy, :merge_to, :move_to]
def index
end
def show def show
@tag = Tag.find_by(tag_params) end
@posts = @tag.posts
def new
@tag = Tag.new
end
def edit
end
def create
@tag = Tag.new(tag_params)
respond_to do |format|
if @tag.save
format.html { redirect_to @tag.show_path, flash: { notice: 'Tag was successfully created.' } }
format.json { render action: 'show', status: :created, location: @tag }
else
format.html { render action: 'new' }
format.json { render json: @tag.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @tag.update(tag_params)
format.html { redirect_to @tag.show_path, flash: { notice: 'Tag was successfully updated.' } }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @tag.errors, status: :unprocessable_entity }
end
end
end
def destroy
@tag.destroy
respond_to do |format|
format.html { redirect_to posts_url, flash: { success: 'Tag successfully deleted.' } }
format.json { head :no_content }
end
end
# このタグを他のタグにマージ
# すべてのPostを移動先のタグに移動し
# このタグを削除する
def merge_to
@merge_to_tag = Tag.find_by(name: params[:merge_to_name]) or raise ActiveRecord::RecordNotFound
@tag.move_all_posts_to!(@merge_to_tag)
@tag.delete
flash[:notice] = "「#{@tag.name}」は「#{@merge_to_tag.name}」にmergeされました"
render json: { status: 'OK' }
end
# このタグを他のタグの下に移動
def move_to
@move_to_tag = Tag.find_by(name: params[:move_to_name]) or raise ActiveRecord::RecordNotFound
@tag.set_parent!(@move_to_tag)
flash[:notice] = "「#{@tag.name}」は「#{@move_to_tag.name}」の下に移動しました"
render json: { status: 'OK' }
end end
private private
def set_tag
tag = Tag.find_by(name: params[:name]) or raise ActiveRecord::RecordNotFound
@tag = tag.decorate
end
def tag_params def tag_params
params.permit(:name).to_hash params.require(:tag).permit(:name, :body).to_hash
end end
end end
class ApisDecorator < Draper::Decorator
delegate_all
# Define presentation-specific methods here. Helpers are accessed through
# `helpers` (aka `h`). You can override attributes, for example:
#
# def created_at
# helpers.content_tag :span, class: 'time' do
# object.created_at.strftime("%a %m/%d/%y")
# end
# end
end
class TagDecorator < Draper::Decorator
delegate_all
# Define presentation-specific methods here. Helpers are accessed through
# `helpers` (aka `h`). You can override attributes, for example:
#
# def created_at
# helpers.content_tag :span, class: 'time' do
# object.created_at.strftime("%a %m/%d/%y")
# end
# end
# tagページのURL
# urlエンコードを施す
def show_path
h.tag_path(name: h.url_encode(model.name))
end
def edit_path
h.edit_tag_path(name: h.url_encode(model.name))
end
# tagをtree viewで表示する
def tree_view_node
_html = ''
_html += %Q{
<a href="#{ self.show_path }" data-name="#{model.name}">
#{model.name} <span class="badge">#{model.posts.count}</span>
</a>
}
_html += '<ul>'
model.children.each do |_child|
_html += '<li>'
_html << _child.decorate.tree_view_node
_html += '</li>'
end
_html += '</ul>'
_html.html_safe
end
end
class TagsDecorator < Draper::CollectionDecorator
# tagをtree viewで表示する
def tree_view
_html = ''
_html += %Q{
<ul class="mod-tag-tree">
<input type="search" class="mod-tag-tree-filter form-control" placeholder="filter...">
}
self.each do |_node|
_html += %Q{
<li>
#{_node.decorate.tree_view_node}
</li>
}
end
_html += "</ul>"
_html.html_safe
end
end
module ApisHelper
end
module PostsHelper module PostsHelper
# @param {ActiveRecord::Relation} node # # @param {ActiveRecord::Relation} node
def h_display_tree(node) # def h_display_tree_old(node)
_html = '<ul>' # _html = '<ul>'
if node.posts.count > 0 # if node.posts.count > 0
_html << %Q{<li><a href="#{ posts_path(q: '#' + node.name) }">#{node.name} <span class="badge">#{node.posts.count}</span></a></li>} # _html << %Q{
end # <li>
node.children.each do |_child| # <a href="#{ posts_path(q: '#' + node.name) }">#{node.name} <span class="badge">#{node.posts.count}</span></a>
_html << h_display_tree(_child) # </li>
end # }
_html << '</ul>' # end
# node.children.each do |_child|
_html.html_safe # _html << h_display_tree(_child)
end # end
# _html << '</ul>'
# _html.html_safe
# end
# # @param {ActiveRecord::Relation} node
# def h_display_tree(node)
# _html = ''
# _html += %Q{
# <a href="#{ posts_path(q: '#' + node.name) }" data-name="#{node.name}">
# #{node.name} <span class="badge">#{node.posts.count}</span>
# </a>
# }
# _html += '<ul>'
# node.children.each do |_child|
# _html += '<li>'
# _html << h_display_tree(_child)
# _html += '</li>'
# end
# _html << '</ul>'
# _html.html_safe
# end
end end
...@@ -39,9 +39,17 @@ class Post < ActiveRecord::Base ...@@ -39,9 +39,17 @@ class Post < ActiveRecord::Base
# generate forked post (not saved) # generate forked post (not saved)
def generate_fork(user) def generate_fork(user)
# `id`以外をコピーする
_forked_post = Post.new(self.attributes.except('id')) _forked_post = Post.new(self.attributes.except('id'))
# `%Name`をユーザー名に置換
_forked_post.title = _forked_post.title.gsub(/%Name/, user.name) _forked_post.title = _forked_post.title.gsub(/%Name/, user.name)
# `%Y`などを日付に変換
_forked_post.title = Time.now.strftime(_forked_post.title) # TODO _forked_post.title = Time.now.strftime(_forked_post.title) # TODO
_forked_post.title = _forked_post.title + ' のコピー'
_forked_post.tag_ids = self.tag_ids _forked_post.tag_ids = self.tag_ids
_forked_post.author = user _forked_post.author = user
......
...@@ -2,7 +2,25 @@ class Tag < ActiveRecord::Base ...@@ -2,7 +2,25 @@ class Tag < ActiveRecord::Base
has_many :post_tags has_many :post_tags
has_many :posts, through: :post_tags has_many :posts, through: :post_tags
# for tree structure
has_ancestry has_ancestry
# for versioning
has_paper_trail
default_scope { order(:updated_at => :desc) } default_scope { order(:updated_at => :desc) }
# 自分のタグに紐づくPostをすべて`other_tag`へ移動する
def move_all_posts_to!(other_tag)
self.posts.each do |_post|
_post.tags.delete(self)
_post.tags << other_tag unless _post.tags.include?(other_tag)
end
end
# 親タグを設定する
def set_parent!(other_tag)
self.parent = other_tag
self.save!
end
end end
<h1>Apis#markdown_preview</h1>
<p>Find me in app/views/apis/markdown_preview.html.erb</p>
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<div class="col-lg-4"> <div class="col-lg-4">
<img class="img-circle" src="http://placehold.it/140x140&amp;text=Rendezvous" alt="Generic placeholder image"> <img class="img-circle" src="http://placehold.it/140x140&amp;text=Rendezvous" alt="Generic placeholder image">
<h2>Rendezvous</h2> <h2>Rendezvous</h2>
<p>Rendezvousはチームの`Stock`と`Flow`の<br>2の情報を蓄積・検索・共有する<br>オープンソースのソフトウェアです。</p> <p>Rendezvousはチームの`Stock`と`Flow`の<br>2種類の情報を蓄積・検索・共有する<br>オープンソースのソフトウェアです。</p>
<p><a class="btn btn-lg btn-primary" href="<%= user_omniauth_authorize_path(:google_oauth2) %>" role="button">Sign up with Google</a></p> <p><a class="btn btn-lg btn-primary" href="<%= user_omniauth_authorize_path(:google_oauth2) %>" role="button">Sign up with Google</a></p>
</div><!-- /.col-lg-4 --> </div><!-- /.col-lg-4 -->
<div class="col-lg-4"> <div class="col-lg-4">
......
<!DOCTYPE html> <!DOCTYPE html><!--
Have a nice $$$$$$$\ $$\
$$ __$$\ $$ |
$$ | $$ | $$$$$$\ $$$$$$$\ $$$$$$$ | $$$$$$\ $$$$$$$$\$$\ $$\ $$$$$$\ $$\ $$\ $$$$$$$\
$$$$$$$ |$$ __$$\ $$ __$$\ $$ __$$ |$$ __$$\\____$$ \$$\ $$ |$$ __$$\ $$ | $$ |$$ _____|
$$ __$$< $$$$$$$$ |$$ | $$ |$$ / $$ |$$$$$$$$ | $$$$ _/ \$$\$$ / $$ / $$ |$$ | $$ |\$$$$$$\
$$ | $$ |$$ ____|$$ | $$ |$$ | $$ |$$ ____|$$ _/ \$$$ / $$ | $$ |$$ | $$ | \____$$\
$$ | $$ |\$$$$$$$\ $$ | $$ |\$$$$$$$ |\$$$$$$$\$$$$$$$$\ \$ / \$$$$$$ |\$$$$$$ |$$$$$$$ |
\__| \__| \_______|\__| \__| \_______| \_______\________| \_/ \______/ \______/ \_______/ with your team.
-->
<html lang="ja"> <html lang="ja">
<head> <head>
<title>Rendezvous</title> <title>Rendezvous</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<%= stylesheet_link_tag "application", media: "all" %>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css">
<!-- Optional theme -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap-theme.min.css"> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap-theme.min.css">
<%= stylesheet_link_tag "application", media: "all" %>
<%= render_style %> <%= render_style %>
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
</head> </head>
<body class="<%= params[:controller] %>-<%= params[:action] %>"> <body class="rails-<%= params[:controller] %>-<%= params[:action] %>">
<%= render partial: 'partials/header_notifications' %> <%= render partial: 'partials/header_notifications' %>
<div class="app"> <%- if params[:controller] != 'home' -%>
<%= render partial: 'partials/app_header' %> <%= render partial: 'partials/app_header' %>
<%- end -%>
<div class="container"> <div class="container container-main">
<%= yield %> <%= yield %>
</div><!--/.container--> </div><!--/.container-->
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.string/2.3.3/underscore.string.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script>
...@@ -34,6 +43,7 @@ ...@@ -34,6 +43,7 @@
window.RV.AllTags = JSON.parse('<%= raw Tag.all.pluck(:name).to_json %>'); window.RV.AllTags = JSON.parse('<%= raw Tag.all.pluck(:name).to_json %>');
</script> </script>
<hr>
<footer> <footer>
<div class="container"> <div class="container">
<a href="https://github.com/tadyjp/rendezvous">Github</a> <a href="https://github.com/tadyjp/rendezvous">Github</a>
......
<!DOCTYPE html>
<html lang="ja">
<head>
<title>Rendezvous</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<%= stylesheet_link_tag "application", media: "all" %>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css">
<!-- Optional theme -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap-theme.min.css">
<%= render_style %>
<%= csrf_meta_tags %>
</head>
<body class="<%= params[:controller] %>-<%= params[:action] %>">
<%= render partial: 'partials/header_notifications' %>
<%= yield %>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script>
<script type="text/javascript">
window.RV = window.RV || {};
window.RV.AllTags = JSON.parse('<%= raw Tag.all.pluck(:name).to_json %>');
</script>
<footer>
<div class="container">
<a href="https://github.com/tadyjp/rendezvous">Github</a>
|
<a href="https://twitter.com/tady_jp">@tady_jp</a>
</div>
</footer>
<%= javascript_include_tag "application" %>
<%= yield :footer_js %>
</body>
</html>
<%#
desc:
- appページの上部に
css:
- none
locals:
- none
usage:
render partial: 'partials/header_notifications'
%>
<% if flash[:success] %> <% if flash[:success] %>
<div class="alert alert-success fade in"> <div class="alert alert-success fade in">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button> <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
......
<%= form_for(@post) do |f| %> <div id="post-form">
<%= form_for(@post) do |f| %>
<% if @post.errors.any? %>
<div id="error_explanation"> <% if @post.errors.any? %>
<h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <div id="error_explanation">
<h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% @post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<ul> <div class="row">
<% @post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="row"> <div class="col-md-10">
<div class="field">
<div class="input-group">
<span class="input-group-addon"><%= f.label :title %></span>
<%= f.text_field :title, class: 'form-control mod-mdEditor-title' %>
</div>
</div>
<div class="col-md-10"> <div class="field">
<div class="field"> <div class="input-group">
<div class="input-group"> <span class="input-group-addon"><%= f.label :tags %></span>
<span class="input-group-addon"><%= f.label :title %></span> <%= hidden_field :post, :tags, class: 'mod-mdEditor-tags', style: 'width:300px',
<%= f.text_field :title, class: 'form-control' %> value: @post.tags.map{ |_tag| _tag.name }.join(',') %>
</div>
</div> </div>
</div> </div>
<div class="field"> <div class="col-md-2">
<div class="input-group"> <div class="actions">
<span class="input-group-addon"><%= f.label :tags %></span> <%= f.submit class: 'btn btn-primary js-disable-confirm-unload', id: 'save_button' %>
<%= hidden_field :post, :tags, id: 'post_tags', style: 'width:300px', value: @post.tags.pluck(:name).join(',') %>
</div> </div>
</div> </div>
</div>
<div class="col-md-2">
<div class="actions">
<%= f.submit class: 'btn btn-primary', id: 'save_button' %>
</div>
</div> </div>
</div> <br>
<br> <div class="row">
<div class="row"> <div class="col-xs-6 col-md-6">
<div class="col-xs-6 col-md-6"> <div class="field">
<!-- <%= f.label :body %><br> -->
<div class="field"> <%= f.text_area :body, class: 'mod-mdEditor-body' %>
<!-- <%= f.label :body %><br> --> </div>
<%= f.text_area :body, class: 'autosize editor disable-tab' %>
</div>
</div><!--/span--> </div><!--/span-->
<div class="col-xs-12 col-sm-6 col-md-6"> <div class="col-xs-12 col-sm-6 col-md-6">
<div class="box-text"> <div class="box-text">
<div id="post_preview" class="text-box body viewer github"> <%# TODO: class名の整理 %>
<%#= h_application_format_markdown(sample_body).html_safe %> <div class="text-box body viewer github mod-mdEditor-preview">
</div>
</div> </div>
</div>
</div><!--/span--> </div><!--/span-->
</div><!--/row--> </div><!--/row-->
<% end %> <% end %>
</div>
<% content_for :footer_js do %> <% content_for :footer_js do %>
<script type="text/javascript"> <script type="text/javascript">
$.setConfirmUnload();
$('#post-form').mod_mdEditor({end_point: '/apis/markdown_preview'});
</script> </script>
<% end %> <% end %>
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<%### Post meta ###%> <%### Post meta ###%>
<% @post.tags.each do |tag| %> <% @post.tags.each do |tag| %>
<span class="label label-info"> <span class="label label-info">
<a href="<%= show_tag_path(name: url_encode(tag.name)) %>">#<%= tag.name %></a> <a href="<%= tag.decorate.show_path %>">#<%= tag.name %></a>
</span> </span>
<% end %> <% end %>
<span class="label label-success"> <span class="label label-success">
...@@ -91,6 +91,7 @@ ...@@ -91,6 +91,7 @@
Launch demo modal Launch demo modal
</button> </button>
--> -->
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
......
<h1>新しい投稿</h1> <h1>編集</h1>
<%= render 'form' %> <%= render 'form' %>
...@@ -18,9 +18,7 @@ ...@@ -18,9 +18,7 @@
<div class="tab-pane" id="tab-tree"> <div class="tab-pane" id="tab-tree">
<% cache('tag-tree', :expires_in => 1.hour) do %> <% cache('tag-tree', :expires_in => 1.hour) do %>
<div class="list-group"> <div class="list-group">
<% Tag.roots.each do |root| %> <%= Tag.all.decorate.tree_view %>
<%= h_display_tree(root) %>
<% end %>
</div> </div>
<% end %> <% end %>
</div><!-- /.tab-pane --> </div><!-- /.tab-pane -->
......
<div class="row"> <div class="row">
<div id="xxxxxxxx"> <div>
<%= render partial: 'posts/show_fragment' %> <%= render partial: 'posts/show_fragment' %>
</div> </div>
</div><!--/row--> </div><!--/row-->
<div id="tag-form">
<%= form_for(@tag, url: @tag.show_path) do |f| %>
<% if @tag.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@tag.errors.count, "error") %> prohibited this tag from being saved:</h2>
<ul>
<% @tag.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="row">
<div class="col-md-10">
<div class="field">
<div class="input-group">
<span class="input-group-addon"><%= f.label :name %></span>
<%= f.text_field :name, class: 'form-control' %>
</div>
</div>
</div>
<div class="col-md-2">
<div class="actions">
<%= f.submit class: 'btn btn-primary js-disable-confirm-unload', id: 'save_button' %>
</div>
</div>
</div>
<br>
<div class="row">
<div class="col-xs-6 col-md-6">
<div class="field">
<!-- <%= f.label :body %><br> -->
<%= f.text_area :body, class: 'mod-mdEditor-body' %>
</div>
</div><!--/span-->
<div class="col-xs-12 col-sm-6 col-md-6">
<div class="box-text">
<div id="tag_preview" class="text-box body viewer github mod-mdEditor-preview">
</div>
</div>
</div><!--/span-->
</div><!--/row-->
<% end %>
</div>
<% content_for :footer_js do %>
<script type="text/javascript">
$.setConfirmUnload()
$('#tag-form').mod_mdEditor({end_point: '/apis/markdown_preview'});
</script>
<% end %>
<h1>編集</h1>
<%= render 'form' %>
この ページ は まだ そんざい しません
json.array!(@posts) do |post|
json.extract! post, :title, :body
json.url post_url(post, format: :json)
end
<h1>新しい投稿</h1>
<%= render 'form' %>
<div class="row"> <div class="row">
<h1><%= @tag.name %>についてのページ</h1> <div class="col-xs-8">
<h1><%= @tag.name %>についてのページ</h1>
</div>
<div class="col-xs-4">
<div class="btn-group pull-right">
<a class="btn btn-primary" href="<%= @tag.edit_path %>">
このページを編集する <span class="glyphicon glyphicon-pencil"></span>
</a>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="#" data-toggle="modal" data-target="#modal-merge">Merge to...</a></li>
<li><a href="#" data-toggle="modal" data-target="#modal-moveUnder">Move this tag under...</a></li>
</ul>
</div>
</div>
<div class="col-xs-8"> <div class="col-xs-8">
<div class="panel panel-default"> <div class="panel panel-default">
...@@ -8,7 +26,11 @@ ...@@ -8,7 +26,11 @@
<%= @tag.name %>」とは <%= @tag.name %>」とは
</div> </div>
<div class="panel-body"> <div class="panel-body">
まだ「<%= @tag.name %>」の情報がありません。 <%- if @tag.body.present? -%>
<%= h_application_format_markdown(@tag.body) %>
<%- else -%>
まだ「<%= @tag.name %>」の情報がありません。
<%- end -%>
</div> </div>
</div> </div>
</div> </div>
...@@ -16,11 +38,115 @@ ...@@ -16,11 +38,115 @@
<div class="col-xs-4"> <div class="col-xs-4">
<div class="list-group"> <div class="list-group">
<h2><small><%= @tag.name %>」のタグが付いた記事</small></h2> <h2><small><%= @tag.name %>」のタグが付いた記事</small></h2>
<% @posts.each_with_index do |post, i| %> <% @tag.posts.each_with_index do |post, i| %>
<a href="<%= post_path(post) %>" class="list-group-item post-list"><%= post.title %></a> <a href="<%= post_path(post) %>" class="list-group-item post-list"><%= post.title %></a>
<% end %> <% end %>
</div> </div>
</div> </div>
</div><!--/row--> </div><!--/row-->
<!-- Modal -->
<div class="modal fade" id="modal-merge" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 class="modal-title" id="myModalLabel">Merge to other tag</h4>
</div>
<div class="modal-body">
<h4>マージ先のTagを選んでください</h4>
<div id="merge-tag-tree">
<%= Tag.all.decorate.tree_view %>
</div>
</div>
<% content_for :footer_js do %>
<script type="text/javascript">
$(function(){
$('#merge-tag-tree a').on('click', function(e){
console.log(e)
e.preventDefault();
var $this = $(this);
if(window.confirm('「<%= @tag.name %>」を「' + $this.data('name') + '」にマージます')) {
$.ajax({
type: "POST",
url: "/tags/<%= @tag.name %>/merge_to/" + $this.data('name')
}).done(function(data){
location.href = "/tags/" + $this.data('name');
}).fail(function(data){
alert('error');
});
} else {
return false;
}
})
})
</script>
<% end %>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- Modal -->
<div class="modal fade" id="modal-moveUnder" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 class="modal-title" id="myModalLabel">Merge to other tag</h4>
</div>
<div class="modal-body">
<h4>マージ先のTagを選んでください</h4>
<div id="move-tag-tree">
<%= Tag.all.decorate.tree_view %>
</div>
</div>
<% content_for :footer_js do %>
<script type="text/javascript">
$(function(){
$('#move-tag-tree a').on('click', function(e){
console.log(e)
e.preventDefault();
var $this = $(this);
if(window.confirm('「<%= j @tag.name %>」を「' + $this.data('name') + '」の下に移動します')) {
$.ajax({
type: "POST",
url: "/tags/<%= @tag.name %>/move_to/" + encodeURIComponent($this.data('name'))
}).done(function(data){
location.href = "/tags/<%= j @tag.name %>";
}).fail(function(data){
alert('error');
});
} else {
return false;
}
})
})
</script>
<% end %>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
json.extract! @tag, :name, :body, :created_at, :updated_at
Rendezvous::Application.routes.draw do Rendezvous::Application.routes.draw do
post 'apis/markdown_preview'
root 'home#top', as: 'root' root 'home#top', as: 'root'
post 'posts/preview' => 'posts#preview' get 'posts/:id/fork' => 'posts#fork', as: 'fork_post'
# get 'posts/show_fragment' => 'posts#show_fragment' post 'posts/:id/mail' => 'posts#mail', as: 'mail_post'
get 'posts/:id/fork' => 'posts#fork', as: 'fork_post' post 'posts/:id/comment' => 'posts#comment', as: 'comment_post'
post 'posts/:id/mail' => 'posts#mail', as: 'mail_post'
post 'posts/:id/comment' => 'posts#comment', as: 'comment_post'
resources :posts resources :posts
get 'tags/:name' => 'tags#show', as: 'show_tag' post 'tags/:name/merge_to/:merge_to_name' => 'tags#merge_to', as: 'merge_to_tag'
post 'tags/:name/move_to/:move_to_name' => 'tags#move_to', as: 'move_to_tag'
resources :tags, :param => :name
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' } devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
......
class AddBodyToTag < ActiveRecord::Migration
def change
add_column :tags, :body, :text
end
end
class CreateVersions < ActiveRecord::Migration
def self.up
create_table :versions do |t|
t.string :item_type, :null => false
t.integer :item_id, :null => false
t.string :event, :null => false
t.string :whodunnit
t.text :object
t.datetime :created_at
end
add_index :versions, [:item_type, :item_id]
end
def self.down
remove_index :versions, [:item_type, :item_id]
drop_table :versions
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140106160129) do ActiveRecord::Schema.define(version: 20140302053916) do
create_table "comments", force: true do |t| create_table "comments", force: true do |t|
t.integer "author_id" t.integer "author_id"
...@@ -47,6 +47,7 @@ ActiveRecord::Schema.define(version: 20140106160129) do ...@@ -47,6 +47,7 @@ ActiveRecord::Schema.define(version: 20140106160129) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.string "ancestry" t.string "ancestry"
t.text "body"
end end
add_index "tags", ["ancestry"], name: "index_tags_on_ancestry", using: :btree add_index "tags", ["ancestry"], name: "index_tags_on_ancestry", using: :btree
...@@ -74,4 +75,15 @@ ActiveRecord::Schema.define(version: 20140106160129) do ...@@ -74,4 +75,15 @@ ActiveRecord::Schema.define(version: 20140106160129) do
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
create_table "versions", force: true do |t|
t.string "item_type", null: false
t.integer "item_id", null: false
t.string "event", null: false
t.string "whodunnit"
t.text "object"
t.datetime "created_at"
end
add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id", using: :btree
end end
require 'spec_helper'
describe ApisController do
describe "GET 'markdown_preview'" do
it "returns http success" do
get 'markdown_preview'
response.should be_success
end
end
end
require 'spec_helper'
describe ApisDecorator do
end
require 'spec_helper'
describe TagDecorator do
end
...@@ -4,4 +4,8 @@ FactoryGirl.define do ...@@ -4,4 +4,8 @@ FactoryGirl.define do
factory :tag_ruby, class: Tag do factory :tag_ruby, class: Tag do
name 'ruby' name 'ruby'
end end
factory :tag_java, class: Tag do
name 'java'
end
end end
require 'spec_helper'
# Specs in this file have access to a helper object that includes
# the ApisHelper. For example:
#
# describe ApisHelper do
# describe "string concat" do
# it "concats two strings with spaces" do
# expect(helper.concat_strings("this","that")).to eq("this that")
# end
# end
# end
describe ApisHelper do
pending "add some examples to (or delete) #{__FILE__}"
end
...@@ -21,7 +21,7 @@ describe Post do ...@@ -21,7 +21,7 @@ describe Post do
end end
it 'valid title' do it 'valid title' do
expect(@new_post.title).to eq('sample title') expect(@new_post.title).to eq('sample title のコピー')
end end
it 'valid body' do it 'valid body' do
......
require 'spec_helper' require 'spec_helper'
describe Tag do describe Tag do
pending "add some examples to (or delete) #{__FILE__}" describe '#move_all_posts_to!' do
before :each do
@tag_ruby = Tag.create(name: 'ruby')
@tag_java = Tag.create(name: 'java')
@post1 = Post.create id: 1001, title: 'ruby rspec', tags: [@tag_ruby]
@post2 = Post.create id: 1002, title: 'ruby is better than java', tags: [@tag_ruby, @tag_java]
@post3 = Post.create id: 1003, title: 'java java...', tags: [@tag_java]
end
it 'successfully moved' do
expect(Tag.find_by(name: 'ruby').posts).to have(2).items
expect(Tag.find_by(name: 'java').posts).to have(2).items
@tag_java.move_all_posts_to!(@tag_ruby)
expect(Tag.find_by(name: 'ruby').posts).to have(3).items
expect(Tag.find_by(name: 'java').posts).to have(0).items
end
end
describe '#set_parent!' do
before :each do
@tag_ruby = Tag.create(name: 'ruby')
@tag_lang = Tag.create(name: 'lang')
end
it 'successfully moved' do
expect(@tag_ruby.parent).not_to eq(@tag_lang)
expect(@tag_lang.children).not_to include(@tag_ruby)
@tag_ruby.set_parent!(@tag_lang)
expect(@tag_ruby.parent).to eq(@tag_lang)
expect(@tag_lang.children).to include(@tag_ruby)
end
end
end end
require 'spec_helper'
describe "apis/markdown_preview.html.erb" do
pending "add some examples to (or delete) #{__FILE__}"
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment