月別アーカイブ: 2014年3月

Ruby on Railsを触ってみる ⑩検索機能 Ransackの実装

検索機能を実装してみる。某先輩のブログでRansackというgemが勧められていたので、早速使ってみよう。

Gemfileの編集をしてbundle install

gem 'ransack'

route.rbを修正して、検索用のルーティングを追加する。

  resources :tweets do
    collection do
      get 'import_csv_new'
      post 'import_csv'
      get 'archives/:yyyymm', :action => :archives, :as =>'archives'
      get 'search/:q', :action => :search, :as =>'search'
    end
  end

controllerにアクションを追加します。パラメタで受け取ったキーワードを含む結果で絞り込みます。

  # GET /tweets/search/keyword
  def search
    @tweets = @user.tweet
             .search(:text_cont => params[:q]).result
             .order(:id)
             .page params[:page]
  end

viewファイルsearch.html.erbを作成します。ほぼarchives.html.erbの流用です。

<h1><%= params&#91;:q&#93; %>の検索結果</h1>

<%= render(@tweets) %>
<% if @tweets.count == 0 %>
  <p><%= params&#91;:q&#93; %>に一致するツイートはありませんでした。</p>
<% end %>
<%= paginate (@tweets) %>

とりあえず動かしてみて、キーワードを適当に渡して見る。

もう実装できた。これは簡単。

あとは検索フォームの設置。それっぽくサイドバーに設置してみる。
サイドバーに設置するため、view/layouts/tweets.html.erbを修正する。

        <div class="span3">
          <div class="well sidebar-nav">
            <ul class="nav nav-list">
              <li class="nav-header">Search</li>
              <%= form_tag(search_tweets_path, :method => "get") do %>
                <div class="input-append">
                  <%= text_field_tag("q", params&#91;:q&#93;, :class => "span9") %>
                  <%= submit_tag "検索", :class => "btn"%>
                </div>
              <% end %>
              <li class="nav-header">Archives</li>
              <% @archives.each do |yyyymm, count| %>
              <li><%= link_to ymconv(yyyymm, count.to_s) , archives_tweets_path(:yyyymm => yyyymm)%><li>
              <% end %>
            </ul>
          </div><!--/.well -->
        </div><!--/span-->

こんな感じに。

Rails楽しい。

Add search form twata701/twice GitHub

Ruby on Railsを触ってみる ⑨月別アーカイブ

なんとなくWebアプリっぽくなってきたTwiceだが、Twitterが公式で提供しているアーカイブのダウンロードでは、JSで動作する過去のツイートを参照できる機能が付いている。月別アーカイブと検索機能が付いているため、せめてそれぐらいは実装しないと、ここまで作った甲斐がないというものではないか。

というわけで、月別アーカイブ機能的なものを作りたい。
サイドバーあたりに、投稿があった年月+件数を表示し、選択するとその年月のツイートを参照できる、といった具合にしたい。
コントローラにアーカイブを集計するメソッドを作る。
年月でGroup by するために、timestampをyyyymmに変換し、groupで指定する。

class TweetsController < ApplicationController
  before_action :set_tweet, only: &#91;:show, :edit, :update, :destroy&#93;
  before_action :check_user
  before_action :make_archive

    # ユーザがログインしていなければ、ホームにリダイレクト
    def make_archive
      @user = current_user
      @archives = @user.tweet.group("strftime('%Y%m', tweets.timestamp)")
                             .order("strftime('%Y%m', tweets.timestamp) desc")
                             .count
    end
&#91;/ruby&#93;

Tweetページの時のみサイドバーに表示したいので、view/layouts/tweets.html.erbを作成する。
&#91;ruby&#93;
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= content_for?(:title) ? yield(:title) : "Twice" %></title>
    <%= csrf_meta_tags %>

    <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
    <!--&#91;if lt IE 9&#93;>
      <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.6.1/html5shiv.js" type="text/javascript"></script>
    <!&#91;endif&#93;-->

    <%= stylesheet_link_tag "application", :media => "all" %>

    <!-- For third-generation iPad with high-resolution Retina display: -->
    <!-- Size should be 144 x 144 pixels -->
    <%= favicon_link_tag 'apple-touch-icon-144x144-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '144x144' %>

    <!-- For iPhone with high-resolution Retina display: -->
    <!-- Size should be 114 x 114 pixels -->
    <%= favicon_link_tag 'apple-touch-icon-114x114-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '114x114' %>

    <!-- For first- and second-generation iPad: -->
    <!-- Size should be 72 x 72 pixels -->
    <%= favicon_link_tag 'apple-touch-icon-72x72-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '72x72' %>

    <!-- For non-Retina iPhone, iPod Touch, and Android 2.1+ devices: -->
    <!-- Size should be 57 x 57 pixels -->
    <%= favicon_link_tag 'apple-touch-icon-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png' %>

    <!-- For all other devices -->
    <!-- Size should be 32 x 32 pixels -->
    <%= favicon_link_tag 'favicon.ico', :rel => 'shortcut icon' %>

    <%= javascript_include_tag "application" %>
  </head>
  <body>

    <div class="navbar navbar-fluid-top">
      <div class="navbar-inner">
        <div class="container-fluid">
          <a class="btn btn-navbar" data-target=".nav-collapse" data-toggle="collapse">
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </a>
          <a class="brand" href="#">Twice</a>
          <div class="container-fluid nav-collapse">
            <ul class="nav">
              <li><%= link_to "Link1", "/path1"  %></li>
              <li><%= link_to "Link2", "/path2"  %></li>
              <li><%= link_to "Link3", "/path3"  %></li>
              <% if user_signed_in? %>
                <li><%= link_to "ログアウト", (destroy_user_session_path)  %></li>
              <% else %>
                <li><%= link_to "ログイン", (user_omniauth_authorize_path :twitter)  %></li>
              <% end %>
            </ul>
          </div><!--/.nav-collapse -->
        </div>
      </div>
    </div>

    <div class="container-fluid">
      <div class="row-fluid">
        <div class="span3">
          <div class="well sidebar-nav">
            <ul class="nav nav-list">
              <li class="nav-header">Archives</li>
              <% @archives.each do |yyyymm, count| %>
              <li><%= ymconv(yyyymm, count.to_s) %><li>
              <% end %>
            </ul>
          </div><!--/.well -->
        </div><!--/span-->
        <div class="span9">
          <%= bootstrap_flash %>
          <%= yield %>
        </div>
      </div><!--/row-->

      <footer>
        <p>&copy; Company 2014</p>
      </footer>

    </div> <!-- /container -->

  </body>
</html>

年月件数表示にはヘルパーメソッドを作成した。
コントローラではyyyyMMと件数という形式で集計結果が出される。’yyyy年MM月’とも出せたのだが、アーカイブ表示画面の遷移に使えなくなりそうなので、単純な形式にした。そこでtweet_helper.rbにymconvというヘルパーメソッドを作り’yyyy年MM月(件数)’という形式の文字列で出力させることでViewをスッキリさせる。

# coding: utf-8
module TweetsHelper
  def ymconv(yyyymm,cnt)
    yyyy = yyyymm[0,4]
    mm = yyyymm[4,2]
    
    return yyyy + "年" + mm + "月 (" + cnt + ")"
  end
end

こんな感じで表示される。

ここからリンクをクリックしたら、その年月のツイートが表示されるようにしたい。
コントローラにメソッドを追加する。
年月のパラメータを受け取って、ツイートを絞り込む。

  # GET /tweets/archives?yyyymm=201402
  def archives
    @user = current_user
    @yyyymm_now = params[:yyyymm]
    @tweets = @user.tweet
             .where("strftime('%Y%m', tweets.timestamp) = '"+@yyyymm_now+"'")
             .order(:id)
             .page params[:page]
  end  

ルーティングも修正する。archivesへのルートを追加。

  resources :tweets do
    collection do
      get 'import_csv_new'
      post 'import_csv'
      get 'archives'
    end
  end

「yyyy年MM月のツイート」と表示したいので、もう一つhelperメソッドを作る。

 
 def ymconvn(yyyymm)
    yyyy = yyyymm[0,4]
    mm = yyyymm[4,2]
    
    return yyyy + "年" + mm + "月 "
  end

これを使ってViewファイル、archives.html.erbを作成する。

<h1><%= ymconvn(@yyyymm_now) %>のツイート一覧</h1>

<%= render(@tweets) %>
<% if @tweets.count == 0 %>
  <p><%= ymconvn(@yyyymm_now) %> のツイートはありません</p>
<% end %>
<%= paginate (@tweets) %>

ツイートがあればこんな感じで表示される。

なければこんな感じ。

add archive in sidebar twata701/twice GitHub

Ruby on Railsを触ってみる ⑧モデルのリレーションシップ

前回の時点で作成したモデルはTweet、Userの2つ。Userはユーザー認証のために作成したが、TweetはUserに紐付いていないため、どのユーザーが登録したツイートかがわからない。そこで、この2つを関連付けたい。

まずはTweetにuser_idを追加する。

$ rails g migration add_user_id_to_tweets user_id:integer

作成されたmigrationファイル。


class AddUserIdToTweets < ActiveRecord::Migration def change add_column :tweets, :user_id, :integer end end [/ruby] rakeコマンドを実行して、テーブルに列を追加する。 [bash] $ rake db:migrate [/bash] 各modelにリレーションシップの記述を追記する。 Tweetは1つのUserに紐付いているためbelongs_toとなる。 [ruby] class Tweet < ActiveRecord::Base belongs_to :user [/ruby] Userは複数のTweetをもつためhas_manyとなる。 [ruby] class User < ActiveRecord::Base devise :trackable, :omniauthable has_many :tweet, dependent: :destroy [/ruby] controllerを修正する。 tweet_controller.rbのindexを修正し、ログイン済みユーザに属するツイートのみの表示とする。また、import_csvの中で、モデルのimport_csvを呼び出す引数に、ログイン中のユーザを追加しておく。 [ruby] def index - @tweets = Tweet.order(:id).page params[:page] + @user = current_user + @tweets = @user.tweet.order(:id).page params[:page] end # GET /tweets/1 @@ -71,7 +72,7 @@ class TweetsController < ApplicationController # POST /tweets/import_csv def import_csv respond_to do |format| - if Tweet.import_csv(params[:csv_file]) + if Tweet.import_csv(params[:csv_file],current_user) format.html { redirect_to tweets_path } format.json { head :no_content } [/ruby] また、modelに記述されているツイートの登録も修正する。 引数にUserモデルを追加する。列user_idにはuserのidをセットする。 また、取り込み前の削除では、userに関連したレコードのみを削除したいので、修正しておく。 差分はこんな感じになる。 [ruby] class Tweet < ActiveRecord::Base - def self.import_csv(csv_file) + belongs_to :user + + def self.import_csv(csv_file,user) # csvファイルを受け取って文字列にする csv_text = csv_file.read @@ -28,7 +30,8 @@ class Tweet < ActiveRecord::Base tweet.retweeted_status_user_id = row[7] tweet.retweeted_status_timestamp = row[8] tweet.expanded_urls = row[9] - + tweet.user_id = user.id + tweet.save end [/ruby] まず、現在登録されているツイートはどのユーザにも属していないので、通常どおりアクセスすると、表示されない。

一度取り込みなおすと、すべて表示されるようになる。

ユーザを変えて取り込みなおすと、ログインしたユーザとして取り込んだツイートが表示される。

Add relationships between Users and Tweets twata701/twice GitHub

Ruby on Rails を触ってみる ⑦renderを使って一覧を整形

現在のツイート一覧はscaffoldで生成されたデフォルトのページのままで、非常に見づらい。ツイート一覧を表示するのが目的で、実際のTwitterのTLとは違い、自分のツイートしかないのでひとまず、①ツイート本文、②ツイート日時の2つの情報のみを表示させよう。

renderを使うのでViewファイル、_tweet.html.erbを作成する。

<div class="tweets">
  <p class="tweet-text"><%= tweet.text %></p>
  <p class="tweet-date"><%= tweet.timestamp %></p>
</div>

viewファイルtweets/index.html.erbは思い切って書き換える。

<h1>ツイート一覧</h1>

<%= render(@tweets) %>

<%= paginate (@tweets) %>

これだけでOK。

一度表示させてみよう。

いい感じに必要な情報だけ表示できるようになった。

ページネーションも問題なし。

twilogを参考に(笑)tweets.css.scssを少しだけ修正。

.tweets {
    border-bottom: 1px dotted #ccc;
    padding: 10px 15px 5px 20px;
    position: relative;

    .tweet-text {
        font-size: 108%;
        color: #444;
        line-height: 1.4;
        margin-bottom: 6px;
        word-wrap: break-word;
        word-break: break-all; 
    }
    
    .tweet-date {
        color: #999;
        font-size: 13px;
    }
}

いい感じに見やすくなったかな。

時間の表示が日本時間ではないので修正したい。
config/application.rbにタイムゾーンの記述を追加する。

$ git diff config/application.rb 

     # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
     # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
-    # config.time_zone = 'Central Time (US & Canada)'
+   config.time_zone = 'Tokyo'

これで日本時間表示になる。

しかし長ったらしい。時刻の表示フォーマットを指定しよう。
Viewファイル、tweets/_tweet.html.erbを修正。

<div class="tweets">
  <p class="tweet-text"><%= tweet.text %></p>
  <p class="tweet-date"><%= tweet.timestamp.strftime("%Y-%m-%d %H:%M:%S") %></p>
</div>

実行してみると、見やすい表示に。

Update list of tweets twata701/twice GitHub

Ruby on Rails を触ってみる ⑥トップページの設定

今のところトップページは、ログインしていてもしてなくてもツイート一覧のページが表示される。せっかくユーザー認証機能を実装したので、ログインしていない→トップページ、ログインしている→ツイート一覧という感じにしたいとおもう。

rails generate でhome/indexを作成する。

$ rails g controller home index

作成されたViewファイル、home/index.html.erbを編集する。

<h1>Twiceへようこそ</h1>
<p>ログインして利用を開始しましょう</p>
<%= link_to (user_omniauth_authorize_path :twitter), class: "btn btn-large btn-primary" do %>
   Twitterでログイン
<% end %>

実行してみる。

ボタンの文字が黒い。Chromeの要素を検証で見てみると、scaffold.cssのa要素が効いてるみたい。調べてみると、bootstrapのリンクとかボタンの要素の文字の色が、scaffold.cssによって効かなくなることがあるみたい。

scaffold.css.scssのa要素のところをひと通りコメントアウトしておく。

/*
a {
  color: #000;
  &:visited {
    color: #666;
  }
  &:hover {
    color: #fff;
    background-color: #000;
  }
}
*/

ちゃんと表示されるようになった。

route.rbを編集して、この画面をrootに指定する。

$ git diff config/routes.rb 
-  root :to => 'tweets#index'
+  root :to => 'home#index'

これでlocalhost:3000/はhome/indexが表示されるようになる。

ログイン後はツイート一覧を表示したいのでcontroller/home_controllers.rbを編集する。


class HomeController < ApplicationController def index if user_signed_in? redirect_to tweets_path end end end [/ruby] これでログインするとrootはtweetに飛ばされる。 逆にログインしていないとhome/indexに飛ばされるようにしたい。 tweet_controller.rbも編集する。 privateのcheck_userメソッドを作成し、before_actionで実行する。 [bash] $ git diff app/controllers/tweets_controller.rb diff --git a/app/controllers/tweets_controller.rb b/app/controllers/tweets_controller.rb index 0a17779..9a6ce3f 100644 --- a/app/controllers/tweets_controller.rb +++ b/app/controllers/tweets_controller.rb @@ -2,6 +2,7 @@ class TweetsController < ApplicationController before_action :set_tweet, only: [:show, :edit, :update, :destroy] + before_action :check_user # GET /tweets # GET /tweets.json @@ -90,4 +91,12 @@ class TweetsController < ApplicationController def tweet_params params.require(:tweet).permit(:tweet_id, :in_reply_to_status_id, :in_reply_to_user_id, :timestamp, :source, :tex end + + # ユーザがログインしていなければ、ホームにリダイレクト + def check_user + unless user_signed_in? + redirect_to :root + end + end + end [/bash] これでログインしていないユーザは強制的にホーム画面にリダイレクトされる。 いい感じ。 set home page and redirection twata701/twice GitHub

参考:
bootstrap-rails で css がうまく適用されない場合の件

Ruby on Railsを触ってみる ⑤rails_configの導入

Omniauthを導入した時に、TwitterのAPIキーをdevise.rbに記述している。このままGithubにPushできないので、.gitignoreに追記している。これを定数のようなもので置き換えたい。

調べてみると、rails_configってGemが定番らしいので導入してみる。

Gemfileの編集とbundle install の実行。

$ git diff Gemfile
diff --git a/Gemfile b/Gemfile
index 587addf..1d3520d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -53,3 +53,6 @@ gem 'kaminari'
 gem 'devise'
 gem 'omniauth-twitter'
 
+# manage environment specific
+gem 'rails_config'
+

$ bundle install

インストールする。

$ rails g rails_config:install

settings.local.ymlに環境変数を記述する。

twitter:
  api_key: twitter_access_token
  api_secret: twitter_access_secret

config/initializers/devise.rbを編集。

  config.omniauth :twitter, Settings.twitter.api_key, Settings.twitter.api_secret, :display => 'popup'
end

.gitignoreからdevise.rbを除外しておく。

これでスッキリした。動作確認をしてみても問題なかったのでOK。
rails config twata701/twice GitHub

参考:
rails_config 使ってみた – Kyuden@Sler

Ruby on Railsを触ってみる ④devise+OmniAuthでTwitter認証

今のままではユーザー関係なく、アップロードされたツイートがすべて参照できてしまう。そこでユーザ認証機能を追加して、ユーザ自身がアップロードしたツイートのみを表示できるようにしたい。

ユーザ認証はdeviseというライブラリが定番らしい。そしてTwitter認証を行うにはOmniAuthを使うのが定番らしい。早速これらを導入してみようと思う。

Gemfileを編集する。

$ git diff Gemfile
diff --git a/Gemfile b/Gemfile
index 0b509d6..587addf 100644
--- a/Gemfile
+++ b/Gemfile
@@ -48,3 +48,8 @@ end
 
 # Pagenation
 gem 'kaminari'
+
+# User Authentication
+gem 'devise'
+gem 'omniauth-twitter'
+

bundle installを行う。

$ bundle install

deviseのインストール。

$ rails g devise:install

認証用モデルUserを作成する。

$ rails g devise user

migrationファイルを編集しておく。TrackableとOmniauthableがあればいいみたいなので、不要な箇所は削除。uidにindexを張る。

class DeviseCreateUsers < ActiveRecord::Migration
  def change
    create_table(:users) do |t|

      ## Trackable
      t.integer  :sign_in_count, :default => 0, :null => false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip
      
      ## Omniauthable
      t.integer :uid, :limit => 8 #bigintにする
      t.string :name
      t.string :provider
      t.string :password

      t.timestamps
    end

    add_index :users, :uid,  :unique => true
  end
end

これでdb:migrateを行う。

$ rake db:migrate

予め取得していたTwitter APIのtokenをconfiginitialize/devise.rbに追記します。

  config.omniauth :twitter, '<ID>', '<SECRET>', :display => 'popup'
end

twitterからのコールバックのためのルーティングを設定する。
また、サインイン後、サインアウト後にrootに飛ばされるため、rootをひとまず設定しておく。

$ git diff config/routes.rb 
diff --git a/config/routes.rb b/config/routes.rb
index 379330e..a18f6b9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -5,6 +5,14 @@ Twice::Application.routes.draw do
       post 'import_csv'
     end
   end
+  
+  devise_for :users, :controllers => { :omniauth_callbacks => "omniauth_callbacks" }
+  root :to => 'tweets#index'
+
+  devise_scope :user do
+    get 'sign_in', :to => 'devise/sessions#new', :as => :new_user_session
+    get 'sign_out', :to => 'devise/sessions#destroy', :as => :destroy_user_session
+  end

コールバック用のコントローラを作成する。
登録済みのユーザの場合は認証、登録済みでない場合はUsersテーブルに登録を行ってくれるはず。

/* omniauth_callbacks_controller.rb */
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def twitter
    # You need to implement the method below in your model
    @user = User.find_for_twitter_oauth(request.env["omniauth.auth"])

    if @user.persisted?
      flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Twitter"
      sign_in_and_redirect @user, :event => :authentication
    else
      session["devise.twitter_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end
end

登録済みかどうかのチェックをUserモデル内に定義する。

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :trackable, :omniauthable
  
  def self.find_for_twitter_oauth(auth)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(name:auth.info.nickname,
                          provider:auth.provider,
                          uid:auth.uid,
                          password:Devise.friendly_token[0,20]
                          )
    end
    user
  end
end

ログインボタンをnavigation barに追加する。

$ git diff app/views/layouts/application.html.erb 
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 67644c1..22313f4 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -52,6 +52,11 @@
               <li><%= link_to "Link1", "/path1"  %></li>
               <li><%= link_to "Link2", "/path2"  %></li>
               <li><%= link_to "Link3", "/path3"  %></li>
+              <% if user_signed_in? %>
+                <li><%= link_to "ログアウト", (destroy_user_session_path)  %></li>
+              <% else %>
+                <li><%= link_to "ログイン", (user_omniauth_authorize_path :twitter)  %></li>
+              <% end %>
             </ul>
           </div><!--/.nav-collapse -->
         </div>

実行画面はこんな感じ。ログインボタンが表示されている。

ログインボタンを押すとおなじみの認証画面。

認証を行うと、ログイン成功のメッセージが表示される。

Usersテーブルを確認すると、ちゃんと追加されていた。

devise+omniauth twata701/twice GitHub

Ruby on Railsを触ってみる ③kaminariでページネーション

1万件のツイートを表示するのに時間がかかりすぎてしまうので、ページネータというのを導入してみる。
kaminariというのが定番らしい。

Gemfileを編集。

$ git diff Gemfile
diff --git a/Gemfile b/Gemfile
index f1167d7..0b509d6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -45,3 +45,6 @@ end
 
 # Use debugger
 # gem 'debugger', group: [:development, :test]
+
+# Pagenation
+gem 'kaminari'

インストールする。

$ bundle install

kaminariのconfigファイルを生成する。

$ rails g kaminari:config

configファイルを編集して表示件数を100件ごとにしておく。
configration/initializers/kaminari_config.rbを編集。

Kaminari.configure do |config|
  # config.default_per_page = 100

controlerのインスタンス変数をセットするところを編集する。

$ git diff app/controllers/tweets_controller.rb 
diff --git a/app/controllers/tweets_controller.rb b/app/controllers/tweets_controller.rb
index 6056733..0a17779 100644
--- a/app/controllers/tweets_controller.rb
+++ b/app/controllers/tweets_controller.rb
@@ -6,7 +6,7 @@ class TweetsController < ApplicationController
   # GET /tweets
   # GET /tweets.json
   def index
-    @tweets = Tweet.all
+    @tweets = Tweet.order(:id).page params&#91;:page&#93;
   end
&#91;/bash&#93;
viewファイルも編集する。テーブルの後ろにリンクを表示するためのメソッドを追加。
&#91;bash&#93;
$ git diff app/views/tweets/index.html.erb 
diff --git a/app/views/tweets/index.html.erb b/app/views/tweets/index.html.erb
index 5c61687..bb1469a 100644
--- a/app/views/tweets/index.html.erb
+++ b/app/views/tweets/index.html.erb
@@ -40,6 +40,4 @@
   </tbody>
 </table>
 
-<br>
-
-<%= link_to 'New Tweet', new_tweet_path %>
+<%= paginate (@tweets) %>

これでこんな感じに。表示時間も気にならないぐらいに短縮された。

kaminariをBootstrapに対応させる。
Bootstrap用のViewを作成。

$ rails g kaminari:views  bootstrap

それっぽくなったで。

とりあえずここまででコミット。
Pagenation twata701/twice GitHub

Ruby on Railsを触ってみる ②CSVの取り込み

前回の続きで、ツイートCSVファイルの読み込み機能を実装してみる。
ActiveRecordを使って、DBにツイートを取り込む。

まずはRoutes.rbの修正。例に習いgit diffで編集箇所を見てみる。
初めて使ったけど、これは便利だな。

$ git diff config/routes.rb 
diff --git a/config/routes.rb b/config/routes.rb
index cf7d04d..379330e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,5 +1,10 @@
 Twice::Application.routes.draw do
-  resources :tweets
+  resources :tweets do
+    collection do
+      get 'import_csv_new'
+      post 'import_csv'
+    end
+  end

import_csv_newとimport_csvというメッソドへのルーティングが追加される。

$ rake routes
               Prefix Verb   URI Pattern                      Controller#Action
import_csv_new_tweets GET    /tweets/import_csv_new(.:format) tweets#import_csv_new
    import_csv_tweets POST   /tweets/import_csv(.:format)     tweets#import_csv
               tweets GET    /tweets(.:format)                tweets#index
                      POST   /tweets(.:format)                tweets#create
            new_tweet GET    /tweets/new(.:format)            tweets#new
           edit_tweet GET    /tweets/:id/edit(.:format)       tweets#edit
                tweet GET    /tweets/:id(.:format)            tweets#show
                      PATCH  /tweets/:id(.:format)            tweets#update
                      PUT    /tweets/:id(.:format)            tweets#update
                      DELETE /tweets/:id(.:format)            tweets#destroy

tweets_controller.rbにアクションを追記する。

  # GET /tweets/import_csv_new
  def import_csv_new
  end  

  # POST /tweets/import_csv
  def import_csv
    respond_to do |format|
      if Tweet.import_csv(params[:csv_file])
        format.html { redirect_to tweets_path }
        format.json { head :no_content }
      else
        format.html { redirect_to tweets_path, :notice => "CSVファイルの読み込みに失敗しました。" }
        format.json { head :no_content }
      end
    end
  end  

import_csv_newは取り込み用のフォームを設置する。
import_csvで受け取ったCSVを取り込む処理を行い、処理が終わるとツイート一覧のページに遷移させる。
Views/tweets/配下に、import_csv_newに対応するViewファイルを作成する。

<h1>Import tweets CSV File</h1>
<%= form_tag('import_csv', :method => 'post', :multipart => true, :class => 'navbar-form pull-left') do %>
    <%= file_field_tag(:csv_file) %>
    <%= submit_tag("CSV Import",:class => 'btn') %>
<% end %>

import_csvをTweetモデルに作成する。


/* Models/tweet.rb */
# coding: utf-8
require ‘csv’ # csv操作を可能にするライブラリ
require ‘kconv’ # 文字コード操作をおこなうライブラリ

class Tweet < ActiveRecord::Base def self.import_csv(csv_file) # csvファイルを受け取って文字列にする csv_text = csv_file.read # 読み込む前に古いツイートをすべて削除する。 Tweet.delete_all #文字列をUTF-8に変換 CSV.parse(Kconv.toutf8(csv_text)) do |row| # ヘッダー行を回避するためにtweet_idが数値でない場合は読み飛ばす if row[0] =~ /\d+/ tweet = Tweet.new tweet.tweet_id = row[0] tweet.in_reply_to_status_id = row[1] tweet.in_reply_to_user_id = row[2] tweet.timestamp = row[3] tweet.source = row[4] tweet.text = row[5] tweet.retweeted_status_id = row[6] tweet.retweeted_status_user_id = row[7] tweet.retweeted_status_timestamp = row[8] tweet.expanded_urls = row[9] tweet.save end end return true end end [/ruby] こんな画面になる。イマイチBootstrapが活用できていないような...

ひとまずアップロードしてみる。
お、遅い。。。
とりあえずこんなかんじで一覧が表示される。

Bootstrapのclassを適用してみる。どうやらこの1万件の全ツイートを表示するのに時間がかかるみたい。

とりあえずここまででコミット。
Add CSV import twata701/twice GitHub

参考:
Rails4 csvファイルをアップロードして読み込む ayaketanのプログラミング勉強日記

Ruby on Railsを触ってみる ①導入

最近Railsを勉強中。
勉強がてら思いついたアプリを作ってみることにした。

この前Twitterのアカウントを移行した時に取得した、全ツイートのCSVファイルがある。これを取り込んで、検索機能をつけたり、年月ごとに参照できるようにしたい。これtwilogでできるんだろうけど、僕の知る限り公開アカウントじゃないと使えなかったので、まずはローカル環境で過去のツイートを参照できるようにしてみよう。

プロジェクト名はtwi○○みたいなのが良かったので、とりあえず辞書引いたら出てきたtwiceにしてプロジェクトを作成する。

$ rails new twice

できた。
早速デフォルトで起動。

$ cd twice
$ rails s

起動画面。
First commit twata701/twice GitHub

某先輩のブログを参考に、.gitignoreを編集する。

$ vi .gitignore
*.rbc
*.sassc
.sass-cache
capybara-*.html
.rspec
/log
/tmp
/db/*.sqlite3
/public/system
/coverage/
/spec/tmp
**.orig
rerun.txt
pickle-email-*.html
config/initializers/secret_token.rb
config/secrets.yml

## Environment normalisation:
/.bundle
/vendor/bundle

# these should all be checked in to normalise the environment:
# Gemfile.lock, .ruby-version, .ruby-gemset

# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc
&#91;/text&#93;
初期コミット。<a href="https://github.com/twata701/twice/commit/a1f9855c529f7dcd48a8efb9025912fc9118601b" title="First commit"  target="_blank">Add tweet by scaffolding twata701/twice GitHub</a>

scaffoldでTweetというモデルをつくる。
CSVの項目に合わせる。

[bash]
$ rails g scaffold Tweet /
> tweet_id:string /
> in_reply_to_status_id:string /
> in_reply_to_user_id:string /
> timestamp:datetime /
> source:text /
> text:text /
> retweeted_status_id:string /
> retweeted_status_user_id:string /
> retweeted_status_timestamp:datetime /
> expanded_urls:string 
[/bash]

DBマイグレートでテーブルの作成。
[bash]
$ rake db:migrate
[/bash]

例に習いコミット。
<a href="https://github.com/twata701/twice/commit/e3748d6c85d6abae0807fbceddcd60502d2f6985" title="Add tweet by scaffolding"  target="_blank">Add tweet by scaffolding  twata701/twice GitHub</a>


例に習いTwitterbootstrapも導入しておく。

gemfileを編集。3行を追加。
[text]
gem 'therubyracer', platforms: :ruby
gem 'less-rails'
gem 'twitter-bootstrap-rails'
$ bundle install
$ rails g bootstrap:install
$ rails g bootstrap:layout application fluid

それっぽくなった。

Add bootstrap twata701/twice GitHub

参考:
2時間で作る実用 Rails アプリ : あかぎメモ