Rodhos Soft

備忘録を兼ねた技術的なメモです。Rofhos SoftではiOSアプリ開発を中心としてAndroid, Webサービス等の開発を承っております。まずはご相談下さい。

チュートリアルをやる。マイクロポスト編

これは以下のチュートリアルをやったことのログである。

railstutorial.jp

ミニメッセージを投稿できるようにしたい。
そのためのモデルををつくる。

  1. データ検証する
  2. 発言したユーザが破棄されれば自動で破棄される。

マイクロポストモデル

rails generate model Micropost content:string user_id:integer

マイグレーションファイルが生成されるので
複合indexを付け加える。

db/migrate/20150917014646_create_microposts.rb

class CreateMicroposts < ActiveRecord::Migration
  def change
    create_table :microposts do |t|
      t.string :content
      t.integer :user_id

      t.timestamps
    end
    add_index :microposts, [:user_id, :created_at]
  end
end

マグレーション

bundle exec rake db:migrate
bundle exec rake db:test:prepare

Userモデルとマイクロポストモデルの関連付け

複数のマイクロポストがユーザと関連づいている。

  1. マイクロポスト側は所属先(belong_to)を持つ
  2. ユーザは発言したマイクロポストを複数所有する(has_many)

app/models/micropost.rb

class Micropost < ActiveRecord::Base
    belongs_to :user
    default_scope -> { order('created_at DESC') }
    validates :user_id, presence: true
end

created_atに関して降順にならぶようにしている。

app/models/user.rb

class User < ActiveRecord::Base
    has_many :microposts
ユーザが削除されるとマイクロポストも破棄されるようにする。
class User < ActiveRecord::Base
    has_many :microposts, dependent: :destroy
....

のように:destroyオプションをつければ良い。

マイクロポストのメッセージ数を制限する。
class Micropost < ActiveRecord::Base
....
    validates :content, presence: true, length: { maximum: 140 }
end

最大140字までに制限。

ユーザページにマイクロポストを表示させる。

ユーザのshow画面の持っているマイクロポストを表示させる。
マイクロポストの部分はパーシャルページで作る。

app/views/users/show.html.erb

<% provide(:title, @user.name) %>
<div class="row">
...
  <aside class="span4">
...
  </aside>

  <div class="span8">
  <% if @user.microposts.any? %>
    <h3>Microposts (<%= @user.microposts.count %>)</h3>
    <ol class="microposts">
      <%= render @microposts %>
    </ol>
    <%= will_paginate @microposts %>
  <% end %>
  </div>

</div>


app/views/microposts/_micropost.html.erb

<li>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  </span>
</li>

コントロ−ラをでデータを入れる。

app/controllers/users_controller.rb

class UsersController < ApplicationController
    
...
    
    def show
        @user = User.find(params[:id])
        @microposts = @user.microposts.paginate(page: params[:page])
    end

....

マイクロポストのサンプルを作る。

lib/tasks/sample_data.rake

namespace :db do
  desc "Fill database with sample data"
  task populate: :environment do
    admin = User.create!(name: "Example User",
                     email: "example@railstutorial.jp",
                     password: "foobar",
                     password_confirmation: "foobar",
                     admin: true)

    99.times do |n|
      name  = Faker::Name.name
      email = "example-#{n+1}@railstutorial.jp"
      password  = "password"
      User.create!(name: name,
                   email: email,
                   password: password,
                   password_confirmation: password)
    end

    users = User.all(limit: 6)
    50.times do
      content = Faker::Lorem.sentence(5)
      users.each { |user| user.microposts.create!(content: content)}
    end
  end
end

データベース更新

 bundle exec rake db:reset
 bundle exec rake db:populate
 bundle exec rake db:test:prepare

ローカルサーバで確認する。

rails server

CSS指定

app/assets/stylesheets/custom.css.scss

....
/* microposts */

.microposts {
  list-style: none;
  margin: 10px 0 0 0;

  li {
    padding: 10px 0;
    border-top: 1px solid #e8e8e8;
  }
}
.content {
  display: block;
}
.timestamp {
  color: $grayLight;
}
.gravatar {
  float: left;
  margin-right: 10px;
}
aside {
  textarea {
    height: 100px;
    margin-bottom: 5px;
  }
}

マイクロポスト操作用のコントローラ作成

ルーティング

config/routes.rb

SampleApp::Application.routes.draw do
    resources :users
    resources :sessions, only: [:new, :create, :destroy]
    resources :microposts, only: [:create, :destroy]
....

作成と破棄しかいらない。

signed_in_userメソッドの移動

Userコントローラで使用していたがMicropostコントローラでも使いたいので
SessionsHelperに引っ越す。

module SessionsHelper
... 
    def current_user?(user)
        user == current_user
    end
    
    def signed_in_user
        unless signed_in?
         store_location
         redirect_to signin_url, notice: "Please sign in." unless signed_in?
        end
    end
....   
end
Micropostsコントローラ作成

app/controllers/microposts_controller.rb

class MicropostsController < ApplicationController
    before_action :signed_in_user, only: [:create, :destroy]
    
    def index
    end
    
    def create
   @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      render 'static_pages/home'
    end
    end
    
    def destroy
    end
end
Micropost作成画面作成

app/controllers/microposts_controller.rb

class MicropostsController < ApplicationController
  
...
    
    def create
     @micropost = current_user.microposts.build(micropost_params)
     if @micropost.save
        flash[:success] = "Micropost created!"
        redirect_to root_url
     else
        render 'static_pages/home'
     end
    end
    
..

    private
    
    def micropost_params
        params.require(:micropost).permit(:content)
    end

end

ビュー

<% if signed_in? %>
  <div class="row">
    <aside class="span4">
      <section>
        <%= render 'shared/user_info' %>
      </section>
      <section>
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>
<% else %>

 <div class="center hero-unit">


  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
  </h2>

  <%= link_to "Sign up now!", signup_path, class: "btn btn-large btn-primary" %>
</div>

<%= link_to image_tag("rails.png", alt: "Rails"), 'http://rubyonrails.org/' %>
<% end %>

サインインしていたらマイクロポスト作成画面(パーシャル)がでるようになっている。

ユーザ情報パーシャル
app/views/shared/_user_info.html.erb

<a href="<%= user_path(current_user) %>">
  <%= gravatar_for current_user, size: 52 %>
</a>
<h1>
  <%= current_user.name %>
</h1>
<span>
  <%= link_to "view my profile", current_user %>
</span>
<span>
  <%= pluralize(current_user.microposts.count, "micropost") %>
</span>

マイクロポスト作成用パーシャル
app/views/shared/_micropost_form.html.erb

<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>


@micropostをもらう。
app/controllers/static_pages_controller.rb

class StaticPagesController < ApplicationController
  def home
      @micropost = current_user.microposts.build if signed_in?
  end

....
end

エラー表示の修正

<% if object.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-error">
      The form contains <%= pluralize(object.errors.count, "error") %>.
    </div>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li>* <%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

@userをobjectに変換。(@micropostも渡せるようにしたため)

同様に他でエラーメッセージに@userを渡しているところを修正する。

app/views/users/new.html.erb

<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="span6 offset3">
    <%= form_for(@user) do |f| %>
    <%= render 'shared/error_messages', object: f.object %>
...
    <% end %>
  </div>
</div>

app/views/users/edit.html.erb

<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="span6 offset3">
    <%= form_for(@user) do |f| %>
    <%= render 'shared/error_messages', object: f.object %>

...

    <% end %>

    <%= gravatar_for @user %>
    <a href="http://gravatar.com/emails">change</a>
  </div>
</div>

ローカルで確認

rails server

ポスト画面とポストした一覧(フィード)が同時に表示したい

指定したユーザidのマイクロポストを取得できるようにする。

class User < ActiveRecord::Base 
...
    def feed
        # このコードは準備段階です。
        # 完全な実装は第11章「ユーザーをフォローする」を参照してください。
        Micropost.where("user_id = ?", id)
    end
....    
end


app/controllers/static_pages_controller.rb
>|ruby|
class StaticPagesController < ApplicationController
  def home
    if signed_in?
       @micropost = current_user.microposts.build
       @feed_items = current_user.feed.paginate(page: params[:page])
    end
  end
...
end


フィード全体のパーシャル
app/views/shared/_feed.html.erb

<% if @feed_items.any? %>
  <ol class="microposts">
    <%= render partial: 'shared/feed_item', collection: @feed_items %>
  </ol>
  <%= will_paginate @feed_items %>
<% end %>


フィード一個のパーシャル
app/views/shared/_feed_item.html.erb

<li id="<%= feed_item.id %>">
  <%= link_to gravatar_for(feed_item.user), feed_item.user %>
  <span class="user">
    <%= link_to feed_item.user.name, feed_item.user %>
  </span>
  <span class="content"><%= feed_item.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(feed_item.created_at) %> ago.
  </span>
</li>

homeページにフィード追加
app/views/static_pages/home.html.erb

<% if signed_in? %>
  <div class="row">
...
  <div class="span8">
    <h3>Micropost Feed</h3>
    <%= render 'shared/feed' %>
   </div>
  </div>
<% else %>
...
<% end %>

ここまででローカルサーバで確認

rails server

しかし、このままでは投稿が失敗すると@feed_itemsがないため落ちる。
これを回避するために失敗時に空のインスタンスを入れておく。

app/controllers/microposts_controller.rb

class MicropostsController < ApplicationController
...
    def create
     @micropost = current_user.microposts.build(micropost_params)
     if @micropost.save
        flash[:success] = "Micropost created!"
        redirect_to root_url
     else
        @feed_items = []
        render 'static_pages/home'
     end
    end
    ...
end

マイクロポストの削除

自分の発言のみ削除できるようにする。
app/views/microposts/_micropost.html.erb
>|html


  • <%= micropost.content %>

    Posted <%= time_ago_in_words(micropost.created_at) %> ago.

    <% if current_user?(micropost.user) %>
    <%= link_to "delete", micropost, method: :delete,
    data: { confirm: "You sure?" },
    title: micropost.content %>
    <% end %>
  • フィードにも
    app/views/shared/_feed_item.html.erb

    <li id="<%= feed_item.id %>">
      <%= link_to gravatar_for(feed_item.user), feed_item.user %>
        <span class="user">
          <%= link_to feed_item.user.name, feed_item.user %>
        </span>
        <span class="content"><%= feed_item.content %></span>
        <span class="timestamp">
          Posted <%= time_ago_in_words(feed_item.created_at) %> ago.
        </span>
      <% if current_user?(feed_item.user) %>
        <%= link_to "delete", feed_item, method: :delete,
                                         data: { confirm: "You sure?" },
                                         title: feed_item.content %>
      <% end %>
    </li>
    


    destroyアクション
    app/controllers/microposts_controller.rb

    class MicropostsController < ApplicationController
        before_action :signed_in_user, only: [:create, :destroy]
        before_action :correct_user,   only: :destroy
    
        def index
        end
        
    ....
        
        def destroy
            @micropost.destroy
            redirect_to root_url
        end
        
        private
        
        def micropost_params
            params.require(:micropost).permit(:content)
        end
        
        def correct_user
            @micropost = current_user.microposts.find_by(id: params[:id])
            redirect_to root_url if @micropost.nil?
        end
        
    end
    

    ローカルサーバで確認する

    rails server