OSDN Git Service

add story
authoryasushiito <yas@pen-chan.jp>
Mon, 16 Jul 2012 07:58:47 +0000 (16:58 +0900)
committeryasushiito <yas@pen-chan.jp>
Mon, 16 Jul 2012 07:58:47 +0000 (16:58 +0900)
33 files changed:
app/controllers/comics_controller.rb
app/controllers/stories_controller.rb
app/models/comic.rb
app/models/panel.rb
app/models/story.rb
app/views/comics/_form.html.erb
app/views/comics/browse.html.erb
app/views/comics/index.html.erb
app/views/comics/list.html.erb
app/views/comics/show.html.erb
app/views/panels/_standard.html.erb
app/views/panels/index.html.erb
app/views/panels/list.html.erb
app/views/stories/_editform.html.erb [new file with mode: 0644]
app/views/stories/_newform.html.erb [new file with mode: 0644]
app/views/stories/browse.html.erb [new file with mode: 0644]
app/views/stories/edit.html.erb [new file with mode: 0644]
app/views/stories/edit.js.erb [new file with mode: 0644]
app/views/stories/list.html.erb [new file with mode: 0644]
app/views/stories/new.html.erb [new file with mode: 0644]
app/views/stories/new.js.erb [new file with mode: 0644]
app/views/stories/show.html.erb [new file with mode: 0644]
app/views/system/browse.html.erb
config/routes.rb
spec/cli/create.rb [new file with mode: 0644]
spec/cli/reauth.rb [new file with mode: 0644]
spec/cli/u/comics/create.json [new file with mode: 0644]
spec/cli/u/original_pictures/create.json [new file with mode: 0644]
spec/cli/u/panels/create.json [new file with mode: 0644]
spec/cli/u/stories/create.json [new file with mode: 0644]
spec/cli/update.rb [new file with mode: 0644]
spec/controllers/stories_controller_spec.rb
spec/models/story_spec.rb

index 25b00eb..7a033ee 100644 (file)
@@ -1,4 +1,5 @@
 class ComicsController < ApplicationController
+  layout 'test' if Pettanr::TestLayout
   if Const.run_mode == 0
     before_filter :authenticate_user!, :only => [:new, :create, :edit, :update, :destroy]
   else
@@ -57,23 +58,6 @@ class ComicsController < ApplicationController
     end
   end
   
-  def play
-    c = Comic.show(params[:id], @author)
-    cnt = Panel.count(:conditions => ['comic_id = ?', c.id]).to_i
-    @offset = Comic.offset cnt, params[:offset]
-    @panel_count = Comic.panel_count cnt, params[:count]
-    @comic = Comic.play(params[:id])
-    respond_to do |format|
-      format.html # index.html.erb
-      format.json {
-        render :json => @comic.to_json_play
-      }
-      format.jsonp {
-        render :json => "callback(" + @comic.to_json_play + ");"
-      }
-    end
-  end
-  
   def list
     @comics = Comic.all
 
index 49799e0..376536e 100644 (file)
@@ -1,2 +1,120 @@
 class StoriesController < ApplicationController
+  layout 'test' if Pettanr::TestLayout
+  if Const.run_mode == 0
+    before_filter :authenticate_user!, :only => [:new, :create, :edit, :update, :destroy]
+  else
+    before_filter :authenticate_user!, :only => [:index, :show, :new, :create, :edit, :update, :destroy]
+  end
+  before_filter :authenticate_admin!, :only => [:list, :browse]
+
+  def show
+    @comic = Comic.show(params[:id], @author)
+    cnt = Story.count(:conditions => ['comic_id = ?', @comic.id]).to_i
+    @offset = Story.offset cnt, params[:offset]
+    @panel_count = Story.panel_count cnt, params[:count]
+    @stories = Story.list(@comic, @author, @offset, @panel_count)
+    respond_to do |format|
+      format.html # index.html.erb
+      format.json {
+        render :json => @stories.to_json_list
+      }
+      format.jsonp {
+        render :json => "callback(" + @stories.to_json_list + ");"
+      }
+    end
+  end
+  
+  def list
+    @stories = Story.all
+
+    respond_to do |format|
+      format.html { render layout: 'system' }# index.html.erb
+      format.json { render json: @stories }
+    end
+  end
+
+  def browse
+    @story = Story.find(params[:id])
+
+    respond_to do |format|
+      format.html { render layout: 'system' } # show.html.erb
+      format.json { render json: @story }
+    end
+  end
+  
+  # GET /stories/new
+  # GET /stories/new.js
+  # GET /stories/new.json
+  def new
+    @story = Story.new 
+    @story.supply_default @author
+    @form_opt = {}
+    respond_to do |format|
+      format.html # new.html.erb
+      format.js { @form_opt = {:remote => true} ;  render action: "new" }
+      format.json { render json: @story }
+    end
+  end
+
+  # GET /stories/1/edit
+  # GET /stories/1.js/edit
+  def edit
+    @story = Story.show(params[:id], @author)
+    respond_to do |format|
+      format.html 
+      format.js
+    end
+  end
+
+  # POST /stories
+  # POST /stories.js
+  # POST /stories.json
+  def create
+    @story = Story.new 
+    @story.supply_default @author
+    @story.attributes = params[:story]
+    
+    respond_to do |format|
+      if @story.store
+        format.html { redirect_to action: :show, id: @story.comic_id }
+        format.js { redirect_to action: :show, id: @story.comic_id }
+        format.json { render json: @story.to_json() }
+      else
+        format.html { render action: "new" }
+        format.json { render json: @story.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+  
+  # PUT /stories/1
+  # PUT /stories/1.js
+  # PUT /stories/1.json
+  def update
+    @story = Story.show(params[:id], @author)
+    @story.author_id = @author.id
+    ot = @story.t
+    @story.attributes = params[:story]
+    respond_to do |format|
+      if @story.store ot
+        format.html { redirect_to action: :show, id: @story.comic_id }
+        format.js { redirect_to action: :show, id: @story.comic_id }
+        format.json { head :ok }
+      else
+        format.html { render action: "edit" }
+        format.json { render json: @story.errors, status: :unprocessable_entity }
+      end
+    end
+  end
+
+  # DELETE /stories/1
+  # DELETE /stories/1.js
+  # DELETE /stories/1.json
+  def destroy
+    @story = Story.show(params[:id], @author)
+    @story.destroy_and_shorten
+    respond_to do |format|
+      format.html { redirect_to story_path(@story.comic) }
+      format.json { head :ok }
+    end
+  end
 end
index e8cd7a5..c036b78 100644 (file)
@@ -102,20 +102,6 @@ class Comic < ActiveRecord::Base
     {:include => {:stories => {:panel => {}}, :author => {}}}
   end
   
-  def self.play cid, opt = {}
-    Comic.find(cid, include: [
-      :author, 
-      :panels => [
-        :panel_pictures => :resource_picture, 
-        :speech_balloons =>{:balloons => {}, :speeches => {}}
-      ]
-    ], order: 'panels.t')
-  end
-  
-  def to_json_play
-    self.to_json( :include => {:author => {}, :panels => {:methods => :panel_element}})
-  end
-  
   def self.visible_count
     Comic.count 'visible > 0'
   end
index cdd905f..ecaa027 100644 (file)
@@ -193,6 +193,10 @@ class Panel < ActiveRecord::Base
     self.author_id == author.id
   end
   
+  def usable? au
+    own? au
+  end
+  
   def publish?
     self.publish > 0
   end
index 0f240aa..7ae9f3a 100644 (file)
@@ -10,10 +10,24 @@ class Story < ActiveRecord::Base
   validates :t, :presence => true, :numericality => {:greater_than_or_equal_to => 0}
   
   def supply_default au
+    self.t = nil
     return false unless au
     self.author_id = au.id
   end
   
+  def own? author
+    return false unless author
+    self.author_id == author.id
+  end
+  
+  def self.show sid, au = nil
+    res = Story.find sid
+    if au
+      raise ActiveRecord::Forbidden unless res.own?(au)
+    end
+    res
+  end
+  
   def self.new_t comic_id
     r = Story.max_t(comic_id)
     r.blank? ? 0 : r.to_i + 1
@@ -84,36 +98,95 @@ class Story < ActiveRecord::Base
     end
   end
   
+  def allow?
+    c = self.comic
+    pl = self.panel
+    c and c.own?(self.author) and pl and pl.usable?(self.author)
+  end
+  
   def store old_t = nil
     res = false
+    raise ActiveRecord::Forbidden unless self.allow?
     Story.transaction do
       self.rotate old_t
       res = self.save
-      unless Story.validate_t(self)
-        res = false
+      raise ActiveRecord::Rollback unless res
+      res = Story.validate_t(self) 
+      unless res
+        self.errors.add :t, 'unserialized'
         raise ActiveRecord::Rollback 
       end
     end
     res
   end
   
-  def move_to new_t
-    return true if self.t == new_t
-    if self.t > new_t
-      Panel.update_all('t = t + 1', ['comic_id = ? and (t >= ? and t < ?)', self.comic_id, new_t, self.t])
-    else
-      nf = Panel.find_t(self.comic_id, new_t)
-      max_t = Panel.max_t.to_i self.comic_id
-      new_t = max_t if new_t > max_t
-      Panel.update_all('t = t - 1', ['comic_id = ? and (t > ? and t <= ?)', self.comic_id, self.t, new_t])
+  def destroy_and_shorten
+    Story.transaction do
+      Story.update_all('t = t - 1', ['comic_id = ? and (t > ?)', self.comic_id, self.t])
+      raise ActiveRecord::Rollback unless self.destroy
     end
-    self.t = new_t
-    self.save
   end
   
-  def destroy_and_shorten
-    Panel.update_all('t = t - 1', ['comic_id = ? and (t > ?)', self.comic_id, self.t])
-    self.destroy
+  def self.default_panel_size
+    30
+  end
+  
+  def self.max_panel_size
+    200
   end
   
+  def self.offset cnt, prm = nil
+    offset = prm.to_i
+    offset = cnt - 1 if offset >= cnt
+    offset = cnt - offset.abs if offset < 0
+    offset = 0 if offset < 0
+    offset
+  end
+  
+  def self.panel_count cnt, prm = self.default_panel_size
+    count = prm.to_i
+    count = self.max_panel_size if count > self.max_panel_size
+    count = self.default_panel_size if count < 1
+    count
+  end
+  
+  def self.list comic, author, offset = 0, limit = Story.default_panel_size
+    opt = self.list_opt
+    opt.merge!({:conditions => ['stories.comic_id = ?', comic.id], :order => 'stories.t', :offset => offset, :limit => limit})
+    Story.find(:all, opt)
+  end
+  
+  def self.list_opt
+    {:include => {
+      :author => {}, 
+      :comic => {
+        :author => {}
+      }, 
+      :panel => {
+        :author => {}, 
+        :panel_pictures => {:resource_picture => {:artist => {}}}, 
+        :speech_balloons =>{:balloons => {}, :speeches => {}}
+      }
+    }}
+  end
+  
+  def self.list_json_opt
+    {:include => {
+      :author => {}, 
+      :comic => {
+        :author => {}
+      }, 
+      :panel => {
+        :author => {}, 
+        :panel_pictures => {:resource_picture => {:artist => {}}}, 
+        :speech_balloons =>{:balloons => {}, :speeches => {}}
+      }
+    }}
+  end
+  
+  def to_json_list
+    self.to_json( :include => {:author => {}, :panels => {:methods => :panel_element}})
+  end
+  
+  
 end
index 62a9a0a..c8e1fb6 100644 (file)
     <%= f.label :visible %><br />
     <%= f.collection_select :visible, [['only me', 0], ['everyone', 3]], :last, :first, :html => {:selected => @comic.visible} %>
   </div>
-  <div class="field">
-    <%= f.label :editable %><br />
-    <%= f.collection_select :editable, [['only me', 0], ['everyone', 3]], :last, :first, :html => {:selected => @comic.editable} %>
-  </div>
 
   <div class="actions">
     <%= f.submit %>
index f963352..c9861b9 100644 (file)
 </p>
 
 <p>
-  <b>editable:</b>
-  <%= @comic.editable %>
-</p>
-
-<p>
   <b>author_id:</b>
   <%= @comic.author_id %>
 </p>
index 9302b79..5ea6d81 100644 (file)
@@ -3,8 +3,7 @@
 <% @comics.each do |comic| %>
   <div>
     <div>
-      <%= link_to h(comic.title), :action => :play, :id => comic.id %>
-      一般投稿:<%= comic.disp_editable %>
+      <%= link_to h(comic.title), :controller => 'stories', :action => :show, :id => comic.id %>
     </div>
     <div>
       作家:<%= h comic.author.name %>
index 8045381..189ada4 100644 (file)
@@ -7,7 +7,6 @@
     <th>width</th>
     <th>height</th>
     <th>visible</th>
-    <th>editable</th>
     <th>author_id</th>
     <th>created_at</th>
     <th>updated_at</th>
@@ -21,7 +20,6 @@
     <td><%= comic.width %></td>
     <td><%= comic.height %></td>
     <td><%= comic.visible %></td>
-    <td><%= comic.editable %></td>
     <td><%= link_to comic.author_id, :controller => '/authors', :action => :browse, :id => comic.author_id %></td>
     <td><%= comic.created_at %></td>
     <td><%= comic.updated_at %></td>
index fb358d2..f77abc2 100644 (file)
 </p>
 
 <p>
-  <b>editable:</b>
-  <%= @comic.editable %>
-</p>
-
-<p>
   <b>author_id:</b>
   <%= @comic.author_id %>
 </p>
index e1ae5ad..fff9389 100644 (file)
@@ -1,6 +1,3 @@
-<span>
-t:<%= @panel.t %>
-</span>
 <div class="panel" style="width:<%= @panel.width %>px;height:<%= @panel.height %>px;overflow:hidden; border:solid black <%= @panel.border %>px; background:white;">
   <% @panel.each_element do |elm| %>
     <% if elm.kind_of?(PanelPicture) %>
@@ -21,20 +18,9 @@ t:<%= @panel.t %>
   <% end %>
 </div>
 <span>
-Comic:<%= @panel.comic_id %>
-</span>
-<span>
-resource_picture:<%= @panel.resource_picture_id %>
-</span>
-<span>
 Width:<%= @panel.width %>
 </span>
 <span>
 Height:<%= @panel.height %>
 </span>
-<%= form_for(@panel) do |f| %>
-    <%= f.submit 'move to' %>
-     <%= f.number_field :t %>
-<% end %>
-<%= button_to 'Destroy', @panel, confirm: 'Are you sure?', method: :delete %>
 
index 8b37c7a..f209aca 100644 (file)
@@ -1,7 +1,6 @@
 <h1>Listing panels 最近の投稿</h1>
 <% @panels.each do |panel| %>
   <div>
-    <%= link_to h(panel.comic.title), panel.comic %>t:<%= panel.t %>
 <div class="panel" style="width:<%= panel.width %>px;height:<%= panel.height %>px;overflow:hidden; border:solid black <%= panel.border %>px; background:white;">
   <% panel.panel_pictures.each do |panel_picture| %>
     <div id="vPicture<%= panel_picture.id -%>" class="panel_picture" style="position:relative;  top:<%= panel_picture.y -%>px; left:<%= panel_picture.x -%>px; z-index:<%= panel_picture.z -%>; ">
@@ -18,7 +17,7 @@
     <%= h panel.author.name %> <%= panel.updated_at %>
   </div>
 <% end %>
-<%= link_to 'open form', new_panel_path, :remote => true %>\r
-  <div id="newpanel">\r
-    uploader\r
-  </div>\r
+<%= link_to 'open form', new_panel_path, :remote => true %>
+  <div id="newpanel">
+    uploader
+  </div>
index aaa0945..ded452e 100644 (file)
@@ -1,20 +1,19 @@
 <h1>Listing panels</h1>
-<%= link_to 'open form', new_panel_path, :remote => true %>\r
-  <div id="newpanel">\r
-    uploader\r
-  </div>\r
+<%= link_to 'open form', new_panel_path, :remote => true %>
+  <div id="newpanel">
+    uploader
+  </div>
 
 <table>
   <tr>
     <th>id</th>
-    <th>Comic</th>
     <th>Width</th>
     <th>Height</th>
     <th>Border</th>
     <th>x</th>
     <th>y</th>
     <th>z</th>
-    <th>t</th>
+    <th>publish</th>
     <th>author_id</th>
     <th>created_at</th>
     <th>updated_at</th>
 <% @panels.each do |panel| %>
   <tr>
     <td><%= link_to panel.id, :action => :browse, :id => panel.id %></td>
-    <td><%= link_to panel.comic_id, :controller => 'comics', :action => :browse, :id => panel.comic_id %></td>
     <td><%= panel.width %></td>
     <td><%= panel.height %></td>
     <td><%= panel.border %></td>
     <td><%= panel.x %></td>
     <td><%= panel.y %></td>
     <td><%= panel.z %></td>
-    <td><%= panel.t %></td>
+    <td><%= panel.publish %></td>
     <td><%= link_to panel.author_id, :controller => '/authors', :action => :browse, :id => panel.author_id %></td>
     <td><%= panel.created_at %></td>
     <td><%= panel.updated_at %></td>
diff --git a/app/views/stories/_editform.html.erb b/app/views/stories/_editform.html.erb
new file mode 100644 (file)
index 0000000..9cc6dd4
--- /dev/null
@@ -0,0 +1,28 @@
+<%= form_for(@story) do |f| %>
+  <% if @story.errors.any? %>
+    <div id="error_explanation">
+      <h2><%= pluralize(@story.errors.count, "error") %> prohibited this comic from being saved:</h2>
+
+      <ul>
+      <% @story.errors.full_messages.each do |msg| %>
+        <li><%= msg %></li>
+      <% end %>
+      </ul>
+    </div>
+  <% end %>
+
+  <div class="field">
+    <%= f.hidden_field :comic_id %>
+  </div>
+  <div class="field">
+    <%= f.number_field :t %>
+  </div>
+  <div class="field">
+    <%= f.hidden_field :panel_id %>
+  </div>
+
+  <div class="actions">
+    <%= f.submit %>
+  </div>
+<% end %>
+<%= button_to 'Destroy', @story.panel, confirm: 'Are you sure?', method: :delete %>
diff --git a/app/views/stories/_newform.html.erb b/app/views/stories/_newform.html.erb
new file mode 100644 (file)
index 0000000..cc40113
--- /dev/null
@@ -0,0 +1,27 @@
+<%= form_for(@story, @form_opt) do |f| %>
+  <% if @story.errors.any? %>
+    <div id="error_explanation">
+      <h2><%= pluralize(@story.errors.count, "error") %> prohibited this comic from being saved:</h2>
+
+      <ul>
+      <% @story.errors.full_messages.each do |msg| %>
+        <li><%= msg %></li>
+      <% end %>
+      </ul>
+    </div>
+  <% end %>
+
+  <div class="field">
+    <%= f.number_field :comic_id %>
+  </div>
+  <div class="field">
+    <%= f.number_field :t %>
+  </div>
+  <div class="field">
+    <%= f.number_field :panel_id %>
+  </div>
+
+  <div class="actions">
+    <%= f.submit %>
+  </div>
+<% end %>
diff --git a/app/views/stories/browse.html.erb b/app/views/stories/browse.html.erb
new file mode 100644 (file)
index 0000000..24a7a6c
--- /dev/null
@@ -0,0 +1,28 @@
+<p id="notice"><%= notice %></p>
+
+<p>
+  <b>comic_id:</b>
+  <%= @story.comic_id %>
+</p>
+
+<p>
+  <b>panel_id:</b>
+  <%= @story.panel_id %>
+</p>
+
+<p>
+  <b>t:</b>
+  <%= @story.t %>
+</p>
+
+<p>
+  <b>author_id:</b>
+  <%= @story.author_id %>
+</p>
+
+<p>
+  <b>updated_at:</b>
+  <%= @story.updated_at %>
+</p>
+
+<%= link_to 'Back', :action => :list %>
diff --git a/app/views/stories/edit.html.erb b/app/views/stories/edit.html.erb
new file mode 100644 (file)
index 0000000..9f2b842
--- /dev/null
@@ -0,0 +1 @@
+<%= render 'editform' %>
diff --git a/app/views/stories/edit.js.erb b/app/views/stories/edit.js.erb
new file mode 100644 (file)
index 0000000..29c687f
--- /dev/null
@@ -0,0 +1 @@
+$("#story-update-<%= @story.id -%>").html("<%= escape_javascript(render('editform')) -%>");
diff --git a/app/views/stories/list.html.erb b/app/views/stories/list.html.erb
new file mode 100644 (file)
index 0000000..6fae81c
--- /dev/null
@@ -0,0 +1,29 @@
+<h1>Listing Story</h1>
+
+<table>
+  <tr>
+    <th>id</th>
+    <th>comic_id</th>
+    <th>panel_id</th>
+    <th>t</th>
+    <th>author_id</th>
+    <th>created_at</th>
+    <th>updated_at</th>
+    <th></th>
+  </tr>
+
+<%  @stories.each do |story| %>
+  <tr>
+    <td><%= link_to story.id, :action => :browse, :id => story.id %></td>
+    <td><%= story.comic_id %></td>
+    <td><%= story.panel_id %></td>
+    <td><%= story.t %></td>
+    <td><%= link_to story.author_id, :controller => '/authors', :action => :browse, :id => story.author_id %></td>
+    <td><%= story.created_at %></td>
+    <td><%= story.updated_at %></td>
+    <td>
+    </td>
+  </tr>
+<% end %>
+</table>
+
diff --git a/app/views/stories/new.html.erb b/app/views/stories/new.html.erb
new file mode 100644 (file)
index 0000000..47cd6d9
--- /dev/null
@@ -0,0 +1 @@
+<%= render 'newform' %>
diff --git a/app/views/stories/new.js.erb b/app/views/stories/new.js.erb
new file mode 100644 (file)
index 0000000..e9f6487
--- /dev/null
@@ -0,0 +1 @@
+$("#story-create").html("<%= escape_javascript(render('newform')) -%>");
diff --git a/app/views/stories/show.html.erb b/app/views/stories/show.html.erb
new file mode 100644 (file)
index 0000000..dfa9eff
--- /dev/null
@@ -0,0 +1,25 @@
+<h1><%= h @comic.title %></h1>
+
+<% @stories.each do |story| %>
+  <% @story = story %>
+  <% @panel = story.panel %>
+  <%= render 'panels/standard' %>
+  <% if story.author.id == @author.id -%>
+    <span>
+    t:<%= story.t %>
+    </span>
+<%= button_to 'Destroy', @story, confirm: 'Are you sure?', method: :delete %>
+    <%= render 'editform' %>
+    <%= link_to 'open js', edit_story_path(story), :remote => true %>
+      <div id="story-update-<%= @story.id -%>">
+        t
+      </div>
+  <% end -%>
+<% end %>
+<% if @comic.author.id == @author.id -%>
+  <%= link_to 'add panel', new_story_path, :remote => true %>
+    <div id="story-create">
+      t
+    </div>
+<% end %>
+<%= link_to 'Back', comics_path %>
index 836bacd..f05ec07 100644 (file)
   </tr>
   <tr>
     <td>
+      <%= link_to 'stories', :controller => 'stories', :action => :list %>
+    </td>
+  </tr>
+  <tr>
+    <td>
       <%= link_to 'original_pictures', :controller => 'original_pictures', :action => :list %>
     </td>
   </tr>
index 0eb6117..7761f23 100644 (file)
@@ -90,10 +90,12 @@ Pettanr::Application.routes.draw do
     end
   end
   resources :original_pictures do
+    new do
+      get :new
+    end
     collection do
       get :index
       get :show
-      get :new
       post :create
       get :list
       get :browse
@@ -118,7 +120,25 @@ Pettanr::Application.routes.draw do
       delete :destroy
     end
   end
+  resources :stories do
+    new do
+      get :new
+    end
+    collection do
+      post :create
+      get :list
+      get :browse
+    end
+    member do
+      get :show
+      put :update
+      delete :destroy
+    end
+  end
   resources :comics do #, except: [:new, :edit]
+    new do
+      get :new
+    end
     collection do
       get :index
       get :show
diff --git a/spec/cli/create.rb b/spec/cli/create.rb
new file mode 100644 (file)
index 0000000..7a1588c
--- /dev/null
@@ -0,0 +1,14 @@
+if ARGV.size < 2\r
+  puts 'create.rb subdir controller {filename=create.json}'\r
+  exit\r
+end\r
+domain = 'http://localhost:3000'\r
+user = ARGV[0].to_s\r
+ctl = ARGV[1]\r
+f = ARGV[2] ? ARGV[2].to_s+'_create' : 'create'\r
+path = File.expand_path(File.dirname(__FILE__))\r
+filename = path + "/#{user}/#{ctl}/#{f}.json"\r
+\r
+cmd = "curl -d @#{filename} #{domain}/#{ctl}.json  -X POST -H \"Content-Type: application/json\""\r
+puts cmd\r
+exec cmd\r
diff --git a/spec/cli/reauth.rb b/spec/cli/reauth.rb
new file mode 100644 (file)
index 0000000..84060c2
--- /dev/null
@@ -0,0 +1,31 @@
+if ARGV.size < 2\r
+  puts 'reauth.rb subdir new_auth_token'\r
+  exit\r
+end\r
+require 'json'\r
+path = File.expand_path(File.dirname(__FILE__)) + '/' + (ARGV[0].to_s + '/' +  '*')\r
+puts 'path:'\r
+puts path\r
+Dir.glob path  do |filename|\r
+  if File.directory?(filename)\r
+    puts 'search dir:'\r
+    puts filename\r
+    Dir.glob filename + '/*' do |filename2|\r
+      d = nil\r
+      puts 'replace:'\r
+      puts filename2\r
+      File.open(filename2, 'rb') do |f|\r
+        d = f.read\r
+      end\r
+      j = JSON.parse d\r
+      ot = j["auth_token"]\r
+      b = d.gsub /#{ot}/, ARGV[1].to_s\r
+      File.open(filename2, 'wb') do |f|\r
+        f.write b\r
+      end\r
+    end\r
+  else\r
+    puts 'ignore file:'\r
+    puts filename\r
+  end\r
+end\r
diff --git a/spec/cli/u/comics/create.json b/spec/cli/u/comics/create.json
new file mode 100644 (file)
index 0000000..7b8fabe
--- /dev/null
@@ -0,0 +1,9 @@
+{\r
+  "comic": {\r
+    "title": "コミック作るテスト",\r
+    "width": 400,\r
+    "height": 200,\r
+    "visible": 3\r
+  },\r
+  "auth_token": "Qr2cveaLKqMHA8dME7CN"\r
+}
\ No newline at end of file
diff --git a/spec/cli/u/original_pictures/create.json b/spec/cli/u/original_pictures/create.json
new file mode 100644 (file)
index 0000000..b5f7c8b
--- /dev/null
@@ -0,0 +1,6 @@
+{\r
+  "original_picture": {\r
+    "file": \r
+"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"  },\r
+  "auth_token": "Qr2cveaLKqMHA8dME7CN"\r
+}
\ No newline at end of file
diff --git a/spec/cli/u/panels/create.json b/spec/cli/u/panels/create.json
new file mode 100644 (file)
index 0000000..9eba8b6
--- /dev/null
@@ -0,0 +1,23 @@
+{\r
+  "panel": {\r
+    "width": 400,\r
+    "height": 200,\r
+    "border": 1,\r
+    "x": 0,\r
+    "y": 0,\r
+    "z": 1,\r
+    "publish": 1,\r
+    "panel_pictures_attributes": {\r
+      "new1": {\r
+        "resource_picture_id": 3,\r
+        "x": 10,\r
+        "y": 135,\r
+        "z": 3,\r
+        "t": 0,\r
+        "width": 100,\r
+        "height": 103\r
+      }\r
+    }\r
+  },\r
+  "auth_token": "Qr2cveaLKqMHA8dME7CN"\r
+}
\ No newline at end of file
diff --git a/spec/cli/u/stories/create.json b/spec/cli/u/stories/create.json
new file mode 100644 (file)
index 0000000..577fff4
--- /dev/null
@@ -0,0 +1,7 @@
+{\r
+  "story": {\r
+    "comic_id": 1,\r
+    "panel_id": 1\r
+  },\r
+  "auth_token": "Qr2cveaLKqMHA8dME7CN"\r
+}
\ No newline at end of file
diff --git a/spec/cli/update.rb b/spec/cli/update.rb
new file mode 100644 (file)
index 0000000..8e19b43
--- /dev/null
@@ -0,0 +1,14 @@
+if ARGV.size < 2\r
+  puts 'update.rb subdir controller {filename=update.json}'\r
+  exit\r
+end\r
+domain = 'http://localhost:3000'\r
+user = ARGV[0].to_s\r
+ctl = ARGV[1]\r
+f = ARGV[2] ? ARGV[2].to_s+'_update' : 'update'\r
+path = File.expand_path(File.dirname(__FILE__))\r
+filename = path + "/#{user}/#{ctl}/#{f}.json"\r
+\r
+cmd = "curl -d @#{filename} #{domain}/#{ctl}.json  -X PUT -H \"Content-Type: application/json\""\r
+puts cmd\r
+exec cmd\r
index 23611f1..33e15b0 100644 (file)
 require 'spec_helper'
 #ストーリー
 describe StoriesController do
+  before do
+    Factory :admin
+    @license = Factory :license
+    @user = Factory :user_yas
+    @author = @user.author    #ユーザ作成時に連動して作成される
+    @comic = Factory :comic, :author_id => @user.author.id
+    @panel = Factory :panel, :author_id => @author.id
+  end
+  
 
   describe '閲覧に於いて' do
     before do
-      @comic = Factory :comic, :author_id => @user.author.id
+      @story = Factory :story, :t => 0, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
       Comic.stub(:show).and_return(@comic)
       sign_in @user
     end
     context '事前チェックする' do
+    end
+    context 'つつがなく終わるとき' do
+    end
+    context '作家権限がないとき' do
+    end
+  end
+
+  describe '新規作成フォーム表示に於いて' do
+    before do
+      sign_in @user
+    end
+    context 'つつがなく終わるとき' do
+      it 'ステータスコード200 OKを返す' do
+        get :new
+        response.should be_success 
+      end
+      it '@storyに新規データを用意している' do
+        get :new
+        assigns(:story).should be_a_new(Story)
+      end
+      it 'コマモデルにデフォルト値補充を依頼している' do
+        Story.any_instance.should_receive(:supply_default).exactly(1)
+        get :new
+      end
+      context 'html形式' do
+        it 'newテンプレートを描画する' do
+          get :new
+          response.should render_template("new")
+        end
+      end
+      context 'js形式' do
+        it 'new.jsテンプレートを描画する' do
+          get :new, :format => :js
+          response.should render_template("new")
+        end
+      end
+    end
+    context '作家権限がないとき' do
       before do
-        Panel.stub(:count).and_return(10)
+        sign_out @user
       end
-      it '与えられたoffsetがセットされている' do
-        get :play, :id => @comic.id, :offset => 7
-        assigns(:offset).should eq 7
+      context 'html形式' do
+        it 'ステータスコード302 Foundを返す' do
+          get :new
+          response.status.should eq 302
+        end
+        it 'サインインページへ遷移する' do
+          get :new
+          response.body.should redirect_to '/users/sign_in'
+        end
       end
-      it '省略されると@offsetに0値が入る' do
-        get :play, :id => @comic.id
-        assigns(:offset).should eq 0
+      context 'js形式' do
+        it 'ステータスコード401 Unauthorizedを返す' do
+          get :new, :format => :js
+          response.status.should eq 401
+        end
+        it '応答メッセージにUnauthorizedを返す' do
+          get :new, :format => :js
+          response.message.should match(/Unauthorized/)
+        end
       end
-      it 'コマ数以上が与えられるとコマ数-1が入る' do
-        get :play, :id => @comic.id, :offset => 10
-        assigns(:offset).should eq 9
+    end
+  end
+  
+  describe '新規作成に於いて' do
+    before do
+      @attr = Factory.attributes_for(:story, :t => nil, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id)
+      sign_in @user
+    end
+    context 'つつがなく終わるとき' do
+      it 'モデルに保存依頼する' do
+        Story.any_instance.should_receive(:store).exactly(1)
+        post :create, :story => @attr
       end
-      it '負の値が与えられると末尾(コマ数)からさかのぼった値が入る' do
-        get :play, :id => @comic.id, :offset => -3
-        assigns(:offset).should eq 7
+      it "@storyに作成されたコマを保持していて、それがDBにある" do
+        post :create, :story => @attr
+        assigns(:story).should be_a(Story)
+        assigns(:story).should be_persisted
       end
-      it 'コマ数以上の負の値が与えられると計算できないので0値が入る' do
-        get :play, :id => @comic.id, :offset => -13
-        assigns(:offset).should eq 0
+      context 'html形式' do
+        it 'ステータスコード302 Foundを返す' do
+          Story.any_instance.stub(:store).and_return(true)
+          post :create, :story => @attr
+          response.status.should eq 302
+        end
+        it 'コミックのストーリー表示へ遷移する' do
+#          Story.any_instance.stub(:store).and_return(true)
+          post :create, :story => @attr
+          response.should redirect_to(:action => :show, :id => @attr[:comic_id])
+        end
       end
-      it '与えられたcountがセットされている' do
-        get :play, :id => @comic.id, :count => 18
-        assigns(:panel_count).should eq 18
+      context 'js形式' do
+        it 'ステータスコード302 Foundを返す' do
+          Story.any_instance.stub(:store).and_return(true)
+          post :create, :story => @attr, :format => :js
+          response.status.should eq 302
+        end
+        it 'コミックのストーリー表示へ遷移する' do
+#          Story.any_instance.stub(:store).and_return(true)
+          post :create, :story => @attr, :format => :js
+          response.should redirect_to(:action => :show, :id => @attr[:comic_id])
+        end
       end
-      it '省略されると@countにデフォルト値が入る' do
-        get :play, :id => @comic.id
-        assigns(:panel_count).should eq Comic.default_panel_size
+      context 'json形式' do
+        it 'ステータスコード200 OKを返す' do
+#          Story.any_instance.stub(:store).and_return(true)
+          post :create, :story => @attr, :format => :json
+          response.should be_success 
+        end
+        it '作成されたコマをjsonデータで返す' do
+          post :create, :story => @attr, :format => :json
+          lambda{JSON.parse(response.body)}.should_not raise_error(JSON::ParserError)
+        end
+        it 'データがアレになっている' do
+          post :create, :story => @attr, :format => :json
+          json = JSON.parse response.body
+          json["t"].should eq @story.t
+        end
       end
-      it '最大を超えると@countにデフォルト最大値が入る' do
-        get :play, :id => @comic.id, :count => 1500
-        assigns(:panel_count).should eq Comic.max_panel_size
+    end
+    context '作家権限がないとき' do
+      before do
+        sign_out @user
       end
-      it '不正な値が入ると@countにデフォルト最大値が入る' do
-        get :play, :id => @comic.id, :page_size => 0
-        assigns(:panel_count).should eq Comic.default_panel_size
+      context 'html形式' do
+        it 'ステータスコード302 Foundを返す' do
+          post :create, :story => @attr
+          response.status.should eq 302
+        end
+        it 'サインインページへ遷移する' do
+          post :create, :story => @attr
+          response.body.should redirect_to '/users/sign_in'
+        end
+      end
+      context 'json形式' do
+        it 'ステータスコード401 Unauthorizedを返す' do
+          post :create, :story => @attr, :format => :json
+          response.status.should eq 401
+        end
+        it '応答メッセージにUnauthorizedを返す' do
+          post :create, :story => @attr, :format => :json
+          response.message.should match(/Unauthorized/)
+        end
       end
     end
+    context '検証、保存に失敗した' do
+      before do
+        Story.any_instance.stub(:store).and_return(false)
+      end
+      it "未保存のコマを保持している" do
+        post :create, :story => @attr
+        assigns(:story).should be_a_new(Story)
+      end
+      context 'html形式' do
+        it 'ステータスコード200 OKを返す' do
+          post :create, :story => @attr
+          response.status.should eq 200
+        end
+        it '新規ページを描画する' do
+          post :create, :story => @attr
+          response.should render_template("new")
+        end
+      end
+      context 'json形式' do
+        it 'ステータスコード422 unprocessable_entity を返す' do
+          post :create, :story => @attr, :format => :json
+          response.status.should eq 422
+        end
+        it '応答メッセージUnprocessable Entityを返す' do
+          post :create, :story => @attr, :format => :json
+          response.message.should match(/Unprocessable/)
+        end
+      end
+    end
+  end
+
+  describe '編集フォーム表示に於いて' do
+    before do
+      @story = Factory :story, :author_id => @author.id
+      sign_in @user
+      Story.stub(:show).and_return(@story)
+    end
     context 'つつがなく終わるとき' do
       it 'ステータスコード200 OKを返す' do
-        get :play, :id => @comic.id
-        response.should be_success
+        get :edit, :id => @story.id
+        response.should be_success 
       end
-      it 'ã\82³ã\83\9fã\83\83ã\82¯モデルに単体取得を問い合わせている' do
-        Comic.should_receive(:show).exactly(1)
-        get :play, :id => @comic.id
+      it 'ã\82³ã\83\9eモデルに単体取得を問い合わせている' do
+        Story.should_receive(:show).exactly(1)
+        get :edit, :id => @story.id
       end
-      it '@comicにアレを取得している' do
-        get :play, :id => @comic.id
-        assigns(:comic).id.should eq(@comic.id)
+      it '@storyにデータを用意している' do
+        get :edit, :id => @story.id
+        assigns(:story).should eq @story
       end
       context 'html形式' do
-        it 'playテンプレートを描画する' do
-          get :play, :id => @comic.id
-          response.should render_template("play")
+        it 'editテンプレートを描画する' do
+          get :edit, :id => @story.id
+          response.should render_template("edit")
         end
       end
-      context 'json形式' do
-        it 'jsonデータを返す' do
-          get :play, :id => @comic.id, :format => :json
-          lambda{JSON.parse(response.body)}.should_not raise_error(JSON::ParserError)
+      context 'js形式' do
+        it 'edit.jsテンプレートを描画する' do
+          get :edit, :id => @story.id, :format => :js
+          response.should render_template("edit")
         end
-        it 'データがアレになっている' do
-          get :play, :id => @comic.id, :format => :json
-          json = JSON.parse response.body
-          json["title"].should match(/normal/)
+      end
+    end
+    context '作家権限がないとき' do
+      before do
+        sign_out @user
+      end
+      context 'html形式' do
+        it 'ステータスコード302 Foundを返す' do
+          get :edit, :id => @story.id
+          response.status.should eq 302
+        end
+        it 'サインインページへ遷移する' do
+          get :edit, :id => @story.id
+          response.body.should redirect_to '/users/sign_in'
+        end
+      end
+      context 'js形式' do
+        it 'ステータスコード401 Unauthorizedを返す' do
+          get :edit, :id => @story.id, :format => :js
+          response.status.should eq 401
+        end
+        it '応答メッセージにUnauthorizedを返す' do
+          get :edit, :id => @story.id, :format => :js
+          response.message.should match(/Unauthorized/)
+        end
+      end
+    end
+  end
+
+  describe '更新に於いて' do
+    before do
+      @story = Factory :story, :author_id => @user.author.id
+      @attr = Factory.attributes_for(:story, :author_id => @author.id)
+      sign_in @user
+    end
+    context 'つつがなく終わるとき' do
+      it 'モデルに取得依頼する' do
+        Story.stub(:show).with(any_args).and_return(@story)
+        Story.should_receive(:show).exactly(1)
+        put :update, :id => @story.id, :story => @attr
+      end
+      it 'モデルに保存依頼する' do
+        Story.any_instance.should_receive(:store).exactly(1)
+        put :update, :id => @story.id, :story => @attr
+      end
+      it "@storyに作成されたストーリーを保持していて、それがDBにある" do
+        put :update, :id => @story.id, :story => @attr
+        assigns(:story).should be_a(Story)
+        assigns(:story).should be_persisted
+      end
+      context 'html形式' do
+        it 'ステータスコード302 Foundを返す' do
+          Story.any_instance.stub(:store).and_return(true)
+          put :update, :id => @story.id, :story => @attr
+          response.status.should eq 302
+        end
+        it 'コミックのストーリー表示へ遷移する' do
+#          Story.any_instance.stub(:store).and_return(true)
+          put :update, :id => @story.id, :story => @attr
+          response.should redirect_to(:action => :show, :id => @attr[:comic_id])
+        end
+      end
+      context 'json形式' do
+        it 'ステータスコード200 OKを返す' do
+#          Story.any_instance.stub(:store).and_return(true)
+          put :update, :id => @story.id, :story => @attr, :format => :json
+          response.should be_success 
         end
       end
     end
@@ -87,25 +301,54 @@ describe StoriesController do
       end
       context 'html形式' do
         it 'ステータスコード302 Foundを返す' do
-          get :play, :id => @comic.id
+          put :update, :id => @story.id, :story => @attr
           response.status.should eq 302
         end
         it 'サインインページへ遷移する' do
-          get :play, :id => @comic.id
+          put :update, :id => @story.id, :story => @attr
           response.body.should redirect_to '/users/sign_in'
         end
       end
       context 'json形式' do
         it 'ステータスコード401 Unauthorizedを返す' do
-          get :play, :id => @comic.id, :format => :json
+          put :update, :id => @story.id, :story => @attr, :format => :json
           response.status.should eq 401
         end
         it '応答メッセージにUnauthorizedを返す' do
-          get :play, :id => @comic.id, :format => :json
+          put :update, :id => @story.id, :story => @attr, :format => :json
           response.message.should match(/Unauthorized/)
         end
       end
     end
+    context '検証、保存に失敗した' do
+      before do
+        Story.any_instance.stub(:store).and_return(false)
+      end
+      it "指定のコマを保持している" do
+        put :update, :id => @story.id, :story => @attr
+        assigns(:story).should eq @story
+      end
+      context 'html形式' do
+        it 'ステータスコード200 OKを返す' do
+          put :update, :id => @story.id, :story => @attr
+          response.status.should eq 200
+        end
+        it '編集ページを描画する' do
+          put :update, :id => @story.id, :story => @attr
+          response.should render_template("edit")
+        end
+      end
+      context 'json形式' do
+        it 'ステータスコード422 unprocessable_entity を返す' do
+          put :update, :id => @story.id, :story => @attr, :format => :json
+          response.status.should eq 422
+        end
+        it '応答メッセージUnprocessable Entityを返す' do
+          put :update, :id => @story.id, :story => @attr, :format => :json
+          response.message.should match(/Unprocessable/)
+        end
+      end
+    end
   end
 
 end
index 8bca75c..2d3a769 100644 (file)
@@ -8,6 +8,8 @@ describe Story do
     @user = Factory( :user_yas)
     @author = @user.author
     @artist = Factory :artist_yas, :author_id => @author.id
+    @other_user = Factory( :user_yas)
+    @other_author = @other_user.author
   end
   
   describe '検証に於いて' do
@@ -121,6 +123,216 @@ describe Story do
     end
     
   end
+  
+  describe '作者判定に於いて' do
+    before do
+      @comic = Factory :comic, :author_id => @author.id
+      @panel = Factory :panel, :author_id => @author.id
+      @story = Factory :story, :author_id => @author.id, :comic_id => @comic.id, :panel_id => @panel.id
+      @comico = Factory :comic, :author_id => @other_author.id
+      @panelo = Factory :panel, :author_id => @other_author.id
+      @storyo = Factory :story, :author_id => @other_author.id, :comic_id => @comico.id, :panel_id => @panelo.id
+    end
+    it '自分のストーリーならyes' do
+      @story.own?(@author).should == true
+    end
+    it '他人のストーリーならno' do
+      @storyo.own?(@author).should == false
+    end
+    it '作家が不明ならno' do
+      @story.own?(nil).should == false
+    end
+  end
+  describe '単体取得に於いて' do
+    before do
+      @comic = Factory :comic, :author_id => @author.id
+      @panel = Factory :panel, :author_id => @author.id
+      @story = Factory :story, :author_id => @author.id, :comic_id => @comic.id, :panel_id => @panel.id
+    end
+    context 'オーナー指定がないとき' do
+      it '指定のストーリーを返す' do
+        l = Story.show @story.id, @author
+        l.should eq @story
+      end
+    end
+    context 'オーナー指定のとき' do
+      it '指定のストーリーが自分のものならそれを返す' do
+        l = Story.show @story.id, @author
+        l.should eq @story
+      end
+      context '他人のストーリーを開こうとしたとき' do
+        it '403Forbidden例外を返す' do
+          Story.any_instance.stub(:own?).and_return(false)
+          lambda{
+            Story.show @story.id, @author
+          }.should raise_error(ActiveRecord::Forbidden)
+        end
+      end
+    end
+    context '存在しないストーリーを開こうとしたとき' do
+      it '404RecordNotFound例外を返す' do
+        lambda{
+          Story.show 110, @author
+        }.should raise_error(ActiveRecord::RecordNotFound)
+      end
+    end
+  end
+  
+  describe '一覧取得に於いて' do
+    before do
+      @comic = Factory :comic, :author_id => @author.id
+      @panel = Factory :panel, :author_id => @author.id
+      @story = Factory :story, :author_id => @author.id, :comic_id => @comic.id, :panel_id => @panel.id
+    end
+    context 'offset補正について' do
+      it '文字列から数値に変換される' do
+        Story.offset(100, '8').should eq 8
+      end
+      it 'nilの場合は0になる' do
+        Story.offset(100).should eq 0
+      end
+      #投稿されたコマ数以上の値が指定されたときは、最後のコマだけになる
+      #最後のコマとは、コマ数‐1.
+      it '1件のときオフセット1なら0になる' do
+        Story.offset(1, '1').should eq 0
+      end
+      it '5件のときオフセット5なら4になる' do
+        Story.offset(5, '5').should eq 4
+      end
+      # 負の値が指定されたときは、最後のコマから数えてコマを飛ばして表示する。
+      #-4のときは、最後から4つのコマを表示する。 
+      it '2件のときオフセット-1なら1になる' do
+        Story.offset(2, '-1').should eq 1
+      end
+      it '5件のときオフセット-2なら3になる' do
+        Story.offset(5, '-2').should eq 3
+      end
+      # 最終的なが負になるなど、不正な値が入ったときは0となる。 
+      it '2件のときオフセット-5なら0になる' do
+        Story.offset(2, '-5').should eq 0
+      end
+    end
+    context 'panel_count補正について' do
+      it '文字列から数値に変換される' do
+        Story.panel_count(100, '7').should eq 7
+      end
+      it 'nilの場合はStory.default_panel_sizeになる' do
+        Story.panel_count(100).should eq Story.default_panel_size
+      end
+      it '0以下の場合はStory.default_panel_sizeになる' do
+        Story.panel_count(100, '0').should eq Story.default_panel_size
+      end
+      it 'Story.max_panel_sizeを超えた場合はStory.max_panel_sizeになる' do
+        Story.panel_count(100, '1000').should eq Story.max_panel_size
+      end
+    end
+    it 'リストを返す' do
+      c = Story.list @comic, @author
+      c.should eq [@story]
+    end
+    it 't順で並んでいる' do
+      v = Factory :story, :author_id => @author.id, :comic_id => @comic.id, :panel_id => @panel.id, :t => 1
+      c = Story.list @comic, @author
+      c.should eq [ @story, v]
+    end
+    context 'DBに5件あって1ページの件数を2件に変えたとして' do
+      before do
+        @story2 = Factory :story, :author_id => @author.id, :comic_id => @comic.id, :panel_id => @panel.id, :t => 1
+        @story3 = Factory :story, :author_id => @author.id, :comic_id => @comic.id, :panel_id => @panel.id, :t => 2
+        @story4 = Factory :story, :author_id => @author.id, :comic_id => @comic.id, :panel_id => @panel.id, :t => 3
+        @story5 = Factory :story, :author_id => @author.id, :comic_id => @comic.id, :panel_id => @panel.id, :t => 4
+      end
+      it 'offset=0なら末尾2件を返す' do
+        #時系列で並んでいる
+        c = Story.list( @comic, @author, 0, 2)
+        c.should eq [@story, @story2]
+      end
+      it 'offset=2なら中間2件を返す' do
+        c = Story.list(@comic, @author, 2, 2)
+        c.should eq [@story3, @story4]
+      end
+      it 'offset=4なら先頭1件を返す' do
+        c = Story.list(@comic, @author, 4, 2)
+        c.should eq [@story5]
+      end
+    end
+  end
+  describe 'list関連テーブルプションに於いて' do
+    it 'includeキーを含んでいる' do
+      r = Story.list_opt
+      r.has_key?(:include).should be_true
+    end
+    it '3つの項目を含んでいる' do
+      r = Story.list_opt[:include]
+      r.should have(3).items
+    end
+    it 'コミックを含んでいる' do
+      r = Story.list_opt[:include]
+      r.has_key?(:comic).should be_true
+    end
+      it 'コミックは作家を含んでいる' do
+        r = Story.list_opt[:include]
+        r[:comic].has_key?(:author).should be_true
+      end
+    it '作家を含んでいる' do
+      r = Story.list_opt[:include]
+      r.has_key?(:author).should be_true
+    end
+    it 'コマを含んでいる' do
+      r = Story.list_opt[:include]
+      r.has_key?(:panel).should be_true
+    end
+      it 'コマは作家を含んでいる' do
+        r = Story.list_opt[:include]
+        r[:panel].has_key?(:author).should be_true
+      end
+      it 'コマはコマ絵を含んでいる' do
+        r = Story.list_opt[:include]
+        r[:panel].has_key?(:panel_pictures).should be_true
+      end
+      it 'コマはフキダシを含んでいる' do
+        r = Story.list_opt[:include]
+        r[:panel].has_key?(:speech_balloons).should be_true
+      end
+  end
+  describe 'json一覧出力オプションに於いて' do
+    it 'includeキーを含んでいる' do
+      r = Story.list_json_opt
+      r.has_key?(:include).should be_true
+    end
+    it '3つの項目を含んでいる' do
+      r = Story.list_json_opt[:include]
+      r.should have(3).items
+    end
+    it 'コミックを含んでいる' do
+      r = Story.list_json_opt[:include]
+      r.has_key?(:comic).should be_true
+    end
+      it 'コミックは作家を含んでいる' do
+        r = Story.list_json_opt[:include]
+        r[:comic].has_key?(:author).should be_true
+      end
+    it '作家を含んでいる' do
+      r = Story.list_json_opt[:include]
+      r.has_key?(:author).should be_true
+    end
+    it 'コマを含んでいる' do
+      r = Story.list_json_opt[:include]
+      r.has_key?(:panel).should be_true
+    end
+      it 'コマは作家を含んでいる' do
+        r = Story.list_json_opt[:include]
+        r[:panel].has_key?(:author).should be_true
+      end
+      it 'コマはコマ絵を含んでいる' do
+        r = Story.list_json_opt[:include]
+        r[:panel].has_key?(:panel_pictures).should be_true
+      end
+      it 'コマはフキダシを含んでいる' do
+        r = Story.list_json_opt[:include]
+        r[:panel].has_key?(:speech_balloons).should be_true
+      end
+  end
   describe 't補充値に於いて' do
     before do
       @comic = Factory :comic, :author_id => @author.id
@@ -679,6 +891,43 @@ describe Story do
       end
     end
   end
+  describe '編集許可に於いて' do
+    before do
+      @comic = Factory :comic, :author_id => @author.id
+      @panel = Factory :panel, :author_id => @author.id
+      @story = Factory.build :story, :t => nil, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+    end
+    context 'つつがなく終わるとき' do
+      it 'trueを返す' do
+        r = @story.allow?
+        r.should be_true
+      end
+    end
+    context 'コミックで引っかかるとき' do
+      it 'falseを返す' do
+        @story.comic_id = nil
+        r = @story.allow?
+        r.should be_false
+      end
+      it 'falseを返す' do
+        Comic.any_instance.stub(:own?).with(any_args).and_return(false)
+        r = @story.allow?
+        r.should be_false
+      end
+    end
+    context 'コマで引っかかるとき' do
+      it 'falseを返す' do
+        @story.panel_id = nil
+        r = @story.allow?
+        r.should be_false
+      end
+      it 'falseを返す' do
+        Panel.any_instance.stub(:usable?).with(any_args).and_return(false)
+        r = @story.allow?
+        r.should be_false
+      end
+    end
+  end
   describe '保存に於いて' do
     before do
       @comic = Factory :comic, :author_id => @author.id
@@ -686,6 +935,11 @@ describe Story do
       @story = Factory.build :story, :t => nil, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
     end
     context 'つつがなく終わるとき' do
+      it '編集許可チェックを依頼している' do
+        Story.any_instance.stub(:allow?).with(any_args).and_return(true)
+        Story.any_instance.should_receive(:allow?).with(any_args).exactly(1)
+        r = @story.store
+      end
       it '順序入れ替えを依頼している' do
         Story.any_instance.stub(:rotate).with(any_args).and_return(0)
         Story.any_instance.should_receive(:rotate).with(any_args).exactly(1)
@@ -720,10 +974,41 @@ describe Story do
         @story5 = Factory :story, :t => 4, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
         @story6 = Factory.build :story, :t => 2, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
       end
+      it '既存のt0には変化がない' do
+        @story6.store
+        @story.reload
+        @story.t.should eq 0
+      end
+      it '既存のt1には変化がない' do
+        @story6.store
+        @story2.reload
+        @story2.t.should eq 1
+      end
+      it '既存のt2を3にシフトしている' do
+        @story6.store
+        @story3.reload
+        @story3.t.should eq 3
+      end
+      it '既存のt3を4にシフトしている' do
+        @story6.store
+        @story4.reload
+        @story4.t.should eq 4
+      end
+      it '既存のt5を5にシフトしている' do
+        @story6.store
+        @story5.reload
+        @story5.t.should eq 5
+      end
+      it '新規のt2が作成されている' do
+        @story6.store
+        @story6.reload
+        @story6.t.should eq 2
+      end
       it '他のコミックに影響がない' do
+        @ot = @storyc2.t
         @story6.store
         @storyc2.reload
-        @storyc2.t.should eq ot
+        @storyc2.t.should eq @ot
       end
     end
     context 'テーブルに5件(t:0,1,2,3,4)+他のコミック1件で3を1に移動したとき' do
@@ -812,5 +1097,190 @@ describe Story do
         @storyc2.t.should eq 0
       end
     end
+    #ロールバックテスト。入れ替えが直接DBをいじるので、すべてのケースで確実にロールバックを確認する
+    context 'テーブルに5件(t:0,1,2,3,4)+他のコミック1件で2に挿入したが保存に失敗したとき' do
+      before do
+        Story.any_instance.stub(:save).with(any_args).and_return(false)
+        @comic2 = Factory :comic, :author_id => @author.id
+        @storyc2 = Factory :story, :t => 0, :comic_id => @comic2.id, :panel_id => @panel.id, :author_id => @author.id
+        @story = Factory :story, :t => 0, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story2 = Factory :story, :t => 1, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story3 = Factory :story, :t => 2, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story4 = Factory :story, :t => 3, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story5 = Factory :story, :t => 4, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story6 = Factory.build :story, :t => 2, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+      end
+      it '既存のtに変化がない' do
+        @story6.store
+        @story.reload
+        @story.t.should eq 0
+        @story2.reload
+        @story2.t.should eq 1
+        @story3.reload
+        @story3.t.should eq 2
+        @story4.reload
+        @story4.t.should eq 3
+        @story5.reload
+        @story5.t.should eq 4
+        @storyc2.reload
+        @storyc2.t.should eq 0
+      end
+      it 'falseを返す' do
+        r = @story6.store
+        r.should be_false
+      end
+    end
+    context 'テーブルに5件(t:0,1,2,3,4)+他のコミック1件で3を1に移動したがシリアルチェックに失敗したとき' do
+      before do
+        Story.stub(:validate_t).with(any_args).and_return(false)
+        @comic2 = Factory :comic, :author_id => @author.id
+        @storyc2 = Factory :story, :t => 0, :comic_id => @comic2.id, :panel_id => @panel.id, :author_id => @author.id
+        @story = Factory :story, :t => 0, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story2 = Factory :story, :t => 1, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story3 = Factory :story, :t => 2, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story4 = Factory :story, :t => 3, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story5 = Factory :story, :t => 4, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @ot = @story4.t
+        @story4.t = 1
+      end
+      it '既存のtに変化がない' do
+        @story4.store @ot
+        @story.reload
+        @story.t.should eq 0
+        @story2.reload
+        @story2.t.should eq 1
+        @story3.reload
+        @story3.t.should eq 2
+        @story4.reload
+        @story4.t.should eq 3
+        @story5.reload
+        @story5.t.should eq 4
+        @storyc2.reload
+        @storyc2.t.should eq 0
+      end
+      it 'falseを返す' do
+        r = @story4.store @ot
+        r.should be_false
+      end
+      it 'tにエラーメッセージが入っている' do
+        @story4.store @ot
+        @story4.errors[:t].should_not be_empty
+        @story4.valid?.should be_true
+      end
+    end
+    context '編集不可だったとき' do
+      before do
+        @story = Factory.build :story, :t => 0, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        Story.any_instance.stub(:allow?).and_return(false)
+      end
+      it '403Forbidden例外を返す' do
+        lambda{
+          @story.store
+        }.should raise_error(ActiveRecord::Forbidden)
+      end
+    end
+  end
+  describe '切り詰め処理つき削除に於いて' do
+    before do
+      @comic = Factory :comic, :author_id => @author.id
+      @panel = Factory :panel, :author_id => @author.id
+      @story = Factory :story, :t => 0, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+    end
+    context 'つつがなく終わるとき' do
+      it '削除される' do
+        lambda{
+          @story.destroy_and_shorten
+        }.should change(Story, :count ).by(-1)
+      end
+    end
+    #連携テスト。切り詰めが直接DBをいじる
+    context '2件で先頭を削除したとき' do
+      before do
+        @story2 = Factory :story, :t => 1, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+      end
+      it '行が削除される' do
+        lambda{
+          @story.destroy_and_shorten
+        }.should change(Story, :count ).by(-1)
+      end
+      it '先頭は削除される' do
+        @story.destroy_and_shorten
+        lambda{
+          Story.find @story.id
+        }.should raise_error(ActiveRecord::RecordNotFound)
+      end
+      it '2件目は前に詰められる' do
+        @story.destroy_and_shorten
+        @story2.reload
+        @story2.t.should eq 0
+      end
+    end
+    context '3件で先頭を削除したとき' do
+      before do
+        @story2 = Factory :story, :t => 1, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story3 = Factory :story, :t => 2, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+      end
+      it '行が削除される' do
+        lambda{
+          @story.destroy_and_shorten
+        }.should change(Story, :count ).by(-1)
+      end
+      it '先頭は削除される' do
+        @story.destroy_and_shorten
+        lambda{
+          Story.find @story.id
+        }.should raise_error(ActiveRecord::RecordNotFound)
+      end
+      it '2件目は前に詰められる' do
+        @story.destroy_and_shorten
+        @story2.reload
+        @story2.t.should eq 0
+      end
+      it '3件目は前に詰められる' do
+        @story.destroy_and_shorten
+        @story3.reload
+        @story3.t.should eq 1
+      end
+    end
+    context '5件で3件目を削除したとき' do
+      before do
+        @story2 = Factory :story, :t => 1, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story3 = Factory :story, :t => 2, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story4 = Factory :story, :t => 3, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+        @story5 = Factory :story, :t => 4, :comic_id => @comic.id, :panel_id => @panel.id, :author_id => @author.id
+      end
+      it '行が削除される' do
+        lambda{
+          @story3.destroy_and_shorten
+        }.should change(Story, :count ).by(-1)
+      end
+      it '1件目は変化がない' do
+        @story3.destroy_and_shorten
+        @story.reload
+        @story.t.should eq 0
+      end
+      it '2件目は変化がない' do
+        @story3.destroy_and_shorten
+        @story2.reload
+        @story2.t.should eq 1
+      end
+      it '3件目は削除される' do
+        @story3.destroy_and_shorten
+        lambda{
+          Story.find @story3.id
+        }.should raise_error(ActiveRecord::RecordNotFound)
+      end
+      it '4件目は前に詰められる' do
+        @story3.destroy_and_shorten
+        @story4.reload
+        @story4.t.should eq 2
+      end
+      it '5件目は前に詰められる' do
+        @story3.destroy_and_shorten
+        @story5.reload
+        @story5.t.should eq 3
+      end
+    end
+    #ロールバックテスト。切り詰めが直接DBをいじるので、すべてのケースで確実にロールバックを確認する
   end
 end