ラベル Rails の投稿を表示しています。 すべての投稿を表示
ラベル Rails の投稿を表示しています。 すべての投稿を表示

2015年3月20日金曜日

Googelの検索結果内に検索フォームを表示させる方法

Googelの検索結果内に検索フォームを表示させる方法

Google とかで検索した際に、大きいサイトだと企業名のしたによく検索フォームが表示されているのをよく見かけると思います。

zozo.jpの場合

zozoの検索フォーム

大きな主要サイトだと、Googleが良きに計らってくれて表示させてくれているようですが、

小さなサイトだとなかなかやってくれないようなので、
自分で”もし良かったら、こんな感じでフォーム表示してもらってもいいですか?”的に設定が可能です。

下記より実装方法になります。

実装方法

今回の実装サンプルでは、Ruby on Rails で chema.org と JSON+LDを使っています。

common/_semantic_web.html.erb

<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "WebSite",
  "url": "https://www.example.com/",
  "potentialAction": {
    "@type": "SearchAction",
    "target": "https://www.example.com/search?keyword={keyword}",
    "query-input": "required name=keyword"
  }
}
</script>
* https://www.example.com/の部分はご自分のサイトURLに変更してください

* https://www.example.com/search?keyword={keyword}は自分のサイトの検索用URLを指定してください。

* "query-input": "required name=keyword"は検索フォームに入力されてワードを**keyword**という変数名定義します。そして、**required** をつけることで必須ですよと設定しています。必須でなくても問題ないようであれば、requiredは削除してください。

あとは、共通レイアウト等でrenderして読み込んでください。

layouts/application.html.erb

  (略)

  <%= render('common/semantic_web') %>

  </body>
</html>

まとめ

比較的簡単に設定ができて、SEOにも効果があると思うので、設定してもよいのではないでしょうか。
しかも、検索結果に表示された時にインパクトもありますしね。

参考サイト

今回の実装にあたり参考にしたサイトです。

Goolge 公式

Google ウェブマスター向けブログ

https://developers.google.com/structured-data/slsb-overview

schema.org

http://schema.org/WebSite

http://schema.org/SearchAction

http://schema.org/docs/actions.html

2015年3月3日火曜日

saveで ActiveRecord::ReadOnlyRecordの対処方法

RailsのActiveRecordでsaveしようとしたら、ActiveRecord::ReadOnlyRecordが発生しました。
私の原因として、joinsメソッドを使って取得したレコードに対して、saveしようとしたのがダメなようです。

発生事象

ダメ例を簡単に書くと下記のようになります。

user_items = UserItems.joins(:user)

user_items.each do |user_item|
  user_item.name = 'aaaaa'
  user_item.save ## ここでエラー
end

対処方法

対象方法としては、readonly(false) メソッドを呼び出します。

user_items = UserItems.joins(:user).readonly(false) ## <= 追加

user_items.each do |user_item|
  user_item.name = 'aaaaa'
  user_item.save
end

とやると、saveに成功します。

2015年1月19日月曜日

RailsでActionMailerでファイルを添付する方法

ブラウザから、アップロードされたファイルをメールに添付して送信するようなことを想定しています。

Actionでのメール送信メソッド

送信用 アクションメソッドは以下のようになります。
tmp_file はFile.class のオブジェクトを受け取る想定です。

  def send_mail

    to_mail = params['to_mail']
    tmp_file = params['tmp_file']

    SendsMailer.send_mail(to_email, tmp_file).deliver

  end

ActionMailerの実装

ActionMailerは以下のようになります。

# -*- encoding : utf-8 -*-
class SendsMailer < ActionMailer::Base

  default :from => 'from@example.com', :content_type => 'text/html'

  def send_mail(to_mail, tmp_file = nil)
    attachments[tmp_file.original_filename] = File.read(tmp_file.path) if tmp_file.present?

    mail(:to => to_mail,
         :subject => "メール送信テスト")
  end

end

重要な部分は

attachments[tmp_file.original_filename] = File.read(tmp_file.path)
attachments['ファイル名'] = 添付するファイル

2014年12月10日水曜日

Railsでdatetimes型のvalidatetionをvalidates_timelinessを使ってする方法

Railsでdates や times, datetimesのバリデーションをするメソッドが標準で用意されていないので、下記のGemを導入して対応しました。

その時の、手順メモです。

GitHub adzap/validates_timeliness

インストール

以下をGemfileに追記して、bundle install を実行する。

# Datetime型 validates
# https://github.com/adzap/validates_timeliness/
gem 'validates_timeliness', '~> 3.0'

gemのinstallが終わったら、下記を実行して設定ファイルを生成する。

rails generate validates_timeliness:install

上記を実行すると、下記のファイルが生成される。

create  config/initializers/validates_timeliness.rb
create  config/locales/validates_timeliness.en.yml

実装

# フォーマット確認
validates_datetime :occurred_at

# 18歳以上の確認
validates_date :date_of_birth, :before => lambda { 18.years.ago },
                               :before_message => "must be at least 18 years old"

# 開始日 < 終了日の確認
validates_datetime :finish_time, :after => :start_time # Method symbol

# 今日以降の日付かの確認
# :todayは、config/initializers/validates_timeliness.rbで設定可能。
validates_date :booked_at, :on => :create, :on_or_after => :today 

# AM 9:00~PM5:00の間の確認
validates_time :booked_at, :between => ['9:00am', '5:00pm']
# 同じくAM 9:00~PM5:00の間の確認
validates_time :booked_at, :between => '9:00am'..'5:00pm'

# 確認した時間によってメッセージを切り替える。
validates_time :breakfast_time, :on_or_after => '6:00am',
                                :on_or_after_message => 'must be after opening time',
                                :before => :lunchtime,
                                :before_message => 'must be before lunch time'

config/initializers/validates_timeliness.rb

:nowや:today を使用する場合、下記のコメントアウトを外す。

config.restriction_shorthand_symbols.update(
  :now   => lambda { Time.current },
  :today => lambda { Date.current }
)

config/locales/validates_timeliness.[en|ja].yml

バリデーションのメッセーを日本語に変更したい場合は、config/locales/validates_timeliness.ja.ymlファイルを作成して編集する。

ファイルのなかみのサンプル

ja:
  errors:
    messages:
      invalid_date: "は、日付のフォーマットが間違っています"
      invalid_time: "は、時間のフォーマットが間違っています"
      invalid_datetime: "は、日時のフォーマットが間違っています"
      is_at: "は、%{restriction} を指定してください"
      before: "は、%{restriction} より小さい日時を指定してください"
      on_or_before: "は、%{restriction} 以前の日時を指定してください"
      after: "は、%{restriction} より大きい日時を指定してください"
      on_or_after: "は、%{restriction} 以降の日時を指定してください"
  validates_timeliness:
    error_value_formats:
      date: '%Y-%m-%d'
      time: '%H:%M:%S'
      datetime: '%Y-%m-%d %H:%M:%S'

2014年12月5日金曜日

Railsでファビコンを設定する

よくブラウザのアドレスバーの横やブックマークした時に表示される画像を作って登録しましょうという話です。
主な手順としては、
1. ファビコンの作成
2. ファビコンの登録
です。
それぞれ、下記に説明していきます。

ファビコンの作成

今回ファビコン用の元画像としてこれを使います。
enter image description here
そして、この画像をファビコン用に変換する必要があるので、
下記のサイトでファビコン用の画像に変換してもらいます。
http://favicon.qfor.info/c/
使い方は、画像を選択して、アイコン作成ボタンをクリックすればすぐに作成されます。
enter image description here
正方形ではないので、こんな感じで、ちょん切られて作成されます。

ファビコンの登録

今回は、Railsでの登録なので favicon_link_tagメソッド を使います。
/layouts/application.html.erbに下記を追記します。
<%= favicon_link_tag %>
この設定を行った場合、/favicon.ico を見にいってくれるので。
作成したアイコンのファイル名を favicon.icoにしてpublicフォルダ配下に配置すれば完成です。

2014年11月28日金曜日

Rails でサイトマップ目的のRSSフィードを配信する

概要

GoogleがXMLサイトマップとRSSの両方を送信することを推奨しているようなので、XMLサイトマップと追加でRSSを設定するようにしました。
RSSをwebマスターツールに登録するにあたって、当然のことながらRSS自体を配信できるようにします。

実装

rss配信用のcontrollerを作成するような設計にしました。
そこから、商品であったりブログであったりのRSSを生成していくことを想定しています。

routes.rd

resources :rss, :only => [:index] do
    member do
      get "blog"
    end
  end

rss_controller.rd

# -*- encoding : utf-8 -*-
class RssController < ApplicationController


  def blog

    start_id = params[:id].to_i
    @blogs = Blog.order('updated_at desc')
    @blogs = @blogs.where('id >= ? AND id < ?', start_id, start_id + 500)

  end

end
最大で、500件づつ区切って指定することを想定しています。

blog.rss.builder

xml.instruct! :xml, :version => "1.0"
xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
  xml.channel do
    xml.title 'ブログRSSテスト'
    xml.link 'http://www.sample.com/blogs'
    xml.description "新着ブログのお知らせ"
    xml.language "ja-jp"
    xml.ttl "40"

    @blogs.each do |blog|
      xml.item do
        xml.title blog.title
        xml.description blog.description if blog.description.presents?
        xml.pubDate blog.updated_at.in_time_zone("Asia/Tokyo").to_s(:rfc822)
        xml.guid blog_url(blog.code)
        xml.link blog_url(blog.code)
      end
    end
  end
end
実装は、というような感じです。

webマスターツールへの設定

あとは、webマスターツール > サイトマップ画面 > [サイトマップの追加/テスト] ボタンをクリックして以下のようにURLを指定します。
rss/1/blog.rss
rss/500/blog.rss
rss/1000/blog.rss

2014年11月27日木曜日

RailsでHashからcheckboxを生成する方法

RailsでHashからcheckboxを生成する方法です。
複数チェックボックスに対応することを想定しています。
<%
sex = {
  unisex: 0,
  men: 1,
  women: 2
}
%>
<div class="form-group">
  <%= f.label :sex, '性別' %>
  <% sex.each do |key, value| %>
    <div class="checkbox">
      <%= f.check_box :sex, {checked: false, multiple: true}, value, nil %><%= key %>
    </div>
  <% end %>
</div>
とすると、下記のようなcheckboxが生成されます。
<div class="form-group">
<label for="sex">性別</label>
<div class="checkbox">
  <input name="feature[sex][]" type="checkbox" value="0">unisex
</div>
<div class="checkbox">
  <input name="feature[sex][]" type="checkbox" value="1">men
</div>
<div class="checkbox">
  <input name="feature[sex][]" type="checkbox" value="2">women
</div>
</div>

2014年11月25日火曜日

ActiveRecordのMigrationでAddColumnしたカラムに別カラムの値を代入する。

ActiveRecordのMigrationでAddColumnしたカラムに別カラムの値を代入する方法です。
結果直接、SQLを投げる結論に行き着きました。
def change
  add_column :users, :value2, :integer
  execute 'UPDATE users SET value2 = value1'
end
Usersテーブルにvalue2カラムを追加して、初期値として、value1の値をいれるようなケースを想定しています。

他の方法として、modelを使うパターンもあります。
class User < ActiveRecord::Base; end

def change
  add_column :users, :value2, :integer

  User.all.each do |user|
      user.value2 = user.value1
      user.save
  end
end

2014年11月14日金曜日

Railsでmeta-tagsやOpen Graphを設定する方法

SEO対策として、Railsのmeta-tagsを設定するために、GitHub - kpumuk/meta-tags を使ったやりかたです。

Gemのインストール

Gemfileに下記を追加して、bundle install
# https://github.com/kpumuk/meta-tags
gem 'meta-tags'

書き方

以下の様にset_meta_tagsして、<%= display_meta_tags %>場合のシンプルな例です。
<% set_meta_tags :title => ‘Member Login’ %>
Member Login

<% set_meta_tags :site => ‘Site Title’, :title => ‘Member Login’ %>
Site Title | Member Login

<% set_meta_tags :site => ‘Site Title’, :title => ‘Member Login’, :reverse => true %>
Member Login | Site Title

Open Graph

Open Graphの設定は下記のように行います。

書き方

set_meta_tags 
  :title => 'item AAAA',
  :og => {
         :title => "Member Login",
         :type => 'website',
         :url => 'https://www.sample.com/',
         :site_name => 'SITE',
         :description => '説明アアアア',
         :image => 'https://www.sample.com/logo.jpg'
 }
}
とすると、下記のようになります。
<meta content="product" property="og:type" />
<meta content="https://www.sample.com/" property="og:url" />
<meta content="SITE" property="og:site_name" />
<meta content="説明アアアアア" property="og:description" />
<meta content="https://www.sample.com/logo.jpg" property="og:image" />
typeは色々とあるようなので、ページに適宜指定してください。
詳しくは、下記のページを参照ください。
https://developers.facebook.com/docs/reference/opengraph
ちなみに、topページにはwebsite を商品ページには、productを指定したりしています。

app_idの取得

それから、facebookのapp登録をして、app_idを取得します。
Appの登録は下記より行ってください。
https://developers.facebook.com/apps
登録したapp_idをもとに、meta-tagにapp_idも設定します。
<meta property="fb:app_id" content="111111111111111" />
※ 111111111111111部分に取得した、app_idを指定

設定の確認

Open Graphの確認は下記から確認できます。
https://developers.facebook.com/tools/debug/og/object/

Twitter Cards

summaryカード

上記のOpen Graphの設定を行っておくと、Twitter Cardsの指定が、かなり簡単になります。
<%
set_meta_tags :site => 'RESTIR リステア',
                     :reverse => true,
                     :og => {
                       ・・・・・・
                     }
                     :twitter => {
                             :card => 'summary',
                             :site => '@twitter_id',
                     }
%>                    
twitterのcardとsiteを指定すれば、あとの画像や説明は、Open Graphから取得してくれます。

Productカード

Productカードの設定サンプルは下記です。
Open Graphが未設定な状態を想定して書きました。
brand_name = 'BrandA'
item_name = 'ItemA'

set_meta_tags 
  :title => 'item AAAA'
  :twitter => {
                         :card => 'product',
                         :site => '@twitter_id',
                         :domain => 'www.sample.com',
                         :title => "#{brand_name} - #{item_name}",
                         :description => '説明AAAAA',
                         :image => ['http://www.sample.com/item-img.jpg',
                                    {       :width => 207,
                                            :height => 234
                                    },
                         ],
                         :label1 => 'BRAND',
                         :data1 => brand_name,
                         :label2 => 'PRODUCT NAME',
                         :data2 => item_name
                 }
とすると、下記のようなタグが発行されます。
<meta content="product" property="twitter:card" />
<meta content="@twitter_id" property="twitter:site" />
<meta content="www.sample.com" property="twitter:domain" />
<meta content="BrandA - ItemA" property="twitter:title" />
<meta content="説明AAAAA" property="twitter:description" />
<meta content="http://www.sample.com/item-img.jpg" property="twitter:image" />
<meta content="BRAND" property="twitter:label1" />
<meta content="BrandA" property="twitter:data1" />
<meta content="PRODUCT NAME" property="twitter:label2" />
<meta content="ItemA" property="twitter:data2" />
imageに指定する画像サイズは160 x 160pxが推奨されています。
imageのwidthとheightを指定すると、縦横比を維持してくれるようです。
GitHubの公式サイトをみると下記のようにしてしていますが
:image => {
    :_      => "http://example.com/1.png",
    :width  => 100,
    :height => 100,
  }
下記のように指定した方が、きれいにタグが生成されました。
:image => ["http://example.com/1.png",
  {
    :width  => 100,
    :height => 100,
  }
]

その他

SEOの効果出て欲しいです。
経過を見守ります。

参考サイト

Twitter Card

Railsでテキストのhtmlタグを削除する方法

RailsのViewでHTMLタグを含んだStringからHTMLたぐだけを削除したい場合、strip_tags を使います。

サンプル

html_str = '<p>test</p>'
strip_tags(html_str)
上記のようにすると、以下の結果が帰ってきます。
test

2014年11月13日木曜日

RailsのActive RecordでDISTINCTする方法

商品一覧の中から、重複を除いたブランドIDの一覧を取得したい場合、SQLのDISTINCTの代わりに、
Active Recordではuniqを使います

コード

@items = Item.select('bland_id').uniq

結果

結果として以下のような結果が返却されます
selectしているので、selectに指定したカラムにしかアクセスできません。
<Item bland_id: 3249>
<Item bland_id: 3250>
<Item bland_id: 1737>

使い方

ループでまわした場合は、以下のようにします。
@items.each do |item|
  p item.bland_id
end

2014年11月10日月曜日

Rails3のasstsのコンパイル速度をturbo-sprockets-rails3を使って改善する

Railsのassetsのコンパイルに時間がかかっていたので、改善するために、下記のGemを試してみました。
GitHub - ndbroadbent/turbo-sprockets-rails3
変更があった、assetsのみ再コンパイルしてくれるので、時間が短縮されます。
Rails4からは、この方法が採用されているみたいです。

インストール

Gemfileのassetsグループに下記を入れて、bundle install
group :assets do
  ...
  # turbo-sprockets-rails3
  # https://github.com/ndbroadbent/turbo-sprockets-rails3
  gem 'turbo-sprockets-rails3'
end

有効期限設定

assetsファイルに有効期限をつける場合は、以下のように設定します。
下記では2週間の設定を行っています。
config/environments/production.rb
config.assets.expire_after 2.weeks
インストール&設定は以上なので、とても簡単です。

実行結果

実際に動かしてみたところ、以下のように改善されました。
これだけで、成果がでるのでありがたいです。

環境A

約 55秒 => 30秒

環境B

1分56秒 => 1分6秒

2014年10月31日金曜日

Railsでsitemap_generator使ってS3にサイトマップを作成する方法

Railsでサイトマップを作成して、登録する方法です。
サイトマップの生成は、GitHub - kjvarga/sitemap_generatorのGemを利用して行います。
また、サイトマップ自体はアマゾンのS3に保存しておきます。

インストール

Gemfileに下記を追加して、bundle install

gem 'sitemap_generator'

設定ファイルの作成

下記を実行して、config/sitemap.rb を作成します。

rake sitemap:install

サンプルコード

本来であれば、上記で生成されたconfig/sitemap.rbに設定を書けばよいと思いますが、
rakeでサイトマップ生成とS3のアップロードを行うようにしました。

コードは以下のような感じです。

  namespace :batch do
    desc 'サイトマップを作成して、S3にアップする。'
    task :create_site_map => :environment do |task|
      success_msgs = []
      success_msgs << 'success'

      batch_start_at = Time.now
      puts "run #{task.name} with env: #{Rails.env} : #{batch_start_at}"

      begin

        SitemapGenerator::Sitemap.default_host = 'http://ore-tech.blogspot.jp/'
        SitemapGenerator::Sitemap.public_path = 'tmp/'

        SitemapGenerator::Sitemap.adapter = SitemapGenerator::S3Adapter.new({
            :aws_access_key_id    => 'xxxxxxxxxxxxxxxxxxxxxxxxx',
            :aws_secret_access_key => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
            :aws_region            => 'ap-xxxxxxx-1', # リージョン 東京の場合、ap-northeast-1
            :fog_provider          => 'AWS',
            :fog_directory         => 'bucket-name' # bucket name
        })
        SitemapGenerator::Sitemap.sitemaps_host = 'http://bucket-name.s3-ap-xxxxxxx-1.amazonaws.com'

        site_maps_path = 'sitemap/'
        unless Rails.env == 'production'
          site_maps_path = "sitemap/#{Rails.env}/"
        end

        SitemapGenerator::Sitemap.sitemaps_path = site_maps_path

        SitemapGenerator::Sitemap.create do

          # home
          add '/home', :changefreq => 'daily', :priority => 0.9

          # contact 
          add '/contact_us', :changefreq => 'weekly'

          # Blog
          group(:sitemaps_path => site_maps_path, :filename => 'blog') do
            Blog.all.each do |blog|
              add blog.url, lastmod: blog.updated_at
            end
          end

        end

        if Rails.env == 'production'
          SitemapGenerator::Sitemap.ping_search_engines
        end

        batch_end_at = Time.now
        puts "end time : #{batch_end_at}"
        puts "Processing time #{batch_end_at - batch_start_at}"

      rescue => e
        puts "error occurred batch : #{Time.now}"
        puts "error occurred batch : #{e.message}"
      end

    end
  end

サイトマップ生成の設定

サイトマップ生成の設定は、主に下記の部分で行っています。

SitemapGenerator::Sitemap.default_host = 'http://ore-tech.blogspot.jp/'
SitemapGenerator::Sitemap.public_path = 'tmp/'

SitemapGenerator::Sitemap.adapter = SitemapGenerator::S3Adapter.new({
    :aws_access_key_id    => 'xxxxxxxxxxxxxxxxxxxxxxxxx',
    :aws_secret_access_key => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    :aws_region            => 'ap-xxxxxxx-1', # リージョン 東京の場合、ap-northeast-1
    :fog_provider          => 'AWS',
    :fog_directory         => 'bucket-name' # bucket name
})
SitemapGenerator::Sitemap.sitemaps_host = 'http://bucket-name.s3-ap-xxxxxxx-1.amazonaws.com'

site_maps_path = 'sitemap/'
unless Rails.env == 'production'
  site_maps_path = "sitemap/#{Rails.env}/"
end

SitemapGenerator::Sitemap.sitemaps_path = site_maps_path

上記設定を行った場合、下記の場所にサイトマップ用のファイルが生成されます。

application側

/tmp/sitemap/blog.xml.gz
/tmp/sitemap/sitemap.xml.gz
/tmp/sitemap/sitemap1.xml.gz

S3側

/bucket-name/sitemap/blog.xml.gz
/bucket-name/sitemap/sitemap.xml.gz
/bucket-name/sitemap/sitemap1.xml.gz

また、下記を行っているので実行されるenv環境によって、生成されるファイルの場所が異なります。

site_maps_path = 'sitemap/'
unless Rails.env == 'production'
  site_maps_path = "sitemap/#{Rails.env}/"
end

production => /bucket-name/sitemap/
staging => /bucket-name/sitemap/staging/blog.xml.gz
development => /bucket-name/sitemap/development/blog.xml.gz

サイトの登録

主には以下の部分で、ページを登録しています。
add メソッドでページ単位で登録していきます。

SitemapGenerator::Sitemap.create do

   # home
   add '/home', :changefreq => 'daily', :priority => 0.9

   # contact 
   add '/contact_us', :changefreq => 'weekly'

   # Blog
   group(:sitemaps_path => site_maps_path, :filename => 'blog') do
     Blog.all.each do |blog|
       add blog.url, :lastmod => blog.updated_at
     end
   end

 end

addメソッドのオプション

:changefreq

ページの予想される更新頻度
デフォルトは、weekly

設定可能な値は以下の通り

  • always:アクセスがあるたびに内容を更新するページに使用します。
  • hourly
  • daily
  • weekly
  • monthly
  • yearly
  • never: アーカイブ ページの URL に使用します。

:priority

サイト内のURLの優先順位を指定します。
0.1(重要でない)~1.0(重要)の範囲で指定します。
デフォルトは 0.5

ここの数値で、掲載順位には影響はないようです。

:lastmod

最終更新日を指定します
デフォルトは Time.now 現在日時

:host

ホストを指定します。
デフォルトはdefault_hostの値。
loginページなど、https://xxxxxなどに変更したい時などに使えると思います。

:expires

有効期限を設定可能です。

group

groupを使うことで、別ファイルで管理することができます。

サイトマップの登録

routes.rb

Googleのウェブマスターツールに /sitemapと登録させるために
routes.rbでS3のサイトマップのパスへリダイレクトさせます。

 match '/sitemap'  =>  redirect('http://backet-name.s3.amazonaws.com/sitemap/sitemap.xml.gz')

robots.txt

検索エンジンのrobot用に以下を追加

Sitemap: http://backet-name.s3.amazonaws.com/sitemap/sitemap.xml.gz

参考サイト

Goolge サイトマップを作成する

2014年10月30日木曜日

wheneverで特定のENV環境のみタスクを追加する方法

RailsのCron管理をしてくれる、Github - javan/whenever で
本番環境だけとか、ステージング環境だけcronのタスクを追加という場合の対処方法です。
@environmentをcase文で分岐する方法です。
以下のサンプルコードでは、production環境では、rake ‘batch:rate’が追加されて、staging環境では、rake ‘batch:test:rate’されます。
サンプルコード
case @environment

  # 本番のみタスク
  when 'production'

    every '0 * * * *' do
      rake 'batch:rate'
    end
  when 'staging'
    every '0 * * * *' do
      rake 'batch:test:rate'
    end

end

2014年10月24日金曜日

multi_dbを使ってレプリケーションのDBに接続を振り分ける方法

最初、レプリケーションされた、DB環境でのマスターDBトスレーブDBの接続の振り分けにOctopusを使おうか考えて色々と調べてみましたが、
スレーブでトラブルが起きた時にも、スレーブDBを参照しにいこうとしてしまうようなので、
それは、いかがなものかと思い、こちらの、Git Hub - multi_dbに切り替えました。
このmulti_dbは、スレーブが停止しても、マスターを見るように切り替わってくれるので、その点安心です。
それでは、設定方法を説明していきます。

database.ymlにスレーブの設定追加

スレーブ用のDBの接続情報をdatabase.ymlに追加します。
追加する、スレーブのdatabase.ymlでの名前の命名ルールは以下のようにします。
[environment]_slave_database[_optional_name]
例えば、production環境の1番スレーブDBの場合は、以下のようにします。
production_slave_database_1
[environment]は、下記のいずれか
  • development
  • staging
  • production
[_optional_name] は、数字がわかりやすいとおもいます。また、スレーブ一台構成の時などは、省略も可能です。
  • _1
  • _2
実際の、database.ymlは以下のようになります。
development:
  adapter: postgresql
  encoding: utf8
  database: development_master
  pool: 5
  username: postgres
  password: <%= ENV['DB_PASSWORD'] %>
  port: 5432
  host: <%= ENV['DB_HOST'] %>
  timeout: 5000

production_slave_database: # that would be a slave
  adapter: postgresql
  encoding: utf8
  database: development_slave
  pool: 5
  username: postgres
  password: <%= ENV['DB_PASSWORD'] %>
  port: 5432
  host: <%= ENV['DB_HOST'] %>
  timeout: 5000

初期化処理の追加

passenger未使用の場合

config/environments/[environment].rbのconfig.after_initializeブロックの中にMultiDb::ConnectionProxy.setup! を追加。
config.after_initialize do
  MultiDb::ConnectionProxy.setup!
end
[environment]は、下記のいずれか
  • development
  • staging
  • production

passenger使用の場合

config/initializers/connection_proxy.rbに以下を追加
if defined?(PhusionPassenger)
  PhusionPassenger.on_event(:starting_worker_process) do |forked|
    if forked
      # ... set MultiDb configuration options, if any ...
      MultiDb::ConnectionProxy.setup!
    end
  end
else # not using passenger (e.g. development/testing)
  # ... set MultiDb configuration options, if any ...
  MultiDb::ConnectionProxy.setup!
end
上記を追加した場合、config/environments/[environment].rbの設定は不要になります。

マスターのみを参照するモデルの指定

UserモデルとBookモデルはマスターしか参照しないという設定にする場合、下記のようにmaster_modelsにモデル名の配列を指定する。
MultiDb::ConnectionProxy.master_models = ['User', 'Book']
MultiDb::ConnectionProxy.setup!

動作確認

これまでの設定で、rails c で
 Blog.find(1)
とすると、下記のような結果が帰ってきます。
[MULTIDB] hijacking connection for Blog
Blog Load (6.6ms)  SELECT "blogs".* FROM "blogs" WHERE "blogs"."id" = $1 LIMIT 1  [["id", 1]]

スレーブの重み付け

複数スレーブ環境の場合、各スレーブに対して重み付けもできるようです。
production_slave_database_1:
  <<: *postgres
  host: my.slavedb_1
  weight: 1

production_slave_database_2:
  <<: *postgres
  host: my.slavedb_2
  weight: 10

production_slave_database_3:
  <<: *postgres
  host: my.slavedb_3
  weight: 5
上記のようにそれぞれに、weightを設定した場合、
リクエスト数が以下のようになる様です。
production_slave_database_1 6302クエリ
production_slave_database_2 62764クエリ
production_slave_database_3 30934クエリ
※上記実際に試していないので、公式ドキュメンとそのままです。
AWSでインスタンスのスペックの違いがあるときなんかは、こちらの設定が有効活用できるような気がします。

その他

テスト環境

テスト環境は、Octopasの時も、書きましたが、読み取り専用のユーザーを作成して簡易的にテストしても良いかと思います。
詳しくは、Octopusを使ってレプリケーションのDBに接続を振り分ける方法を参照してください。
本来であれば、ちゃんとレプリケーション環境で試すのが望ましいとおもいますが、、、、

Octopusを使ってレプリケーションのDBに接続を振り分ける方法

DBがレプリケーションの構成になっている場合、更新系のクエリーをマスターDBへ、参照系のクエリーをスレーブDBへクエリを投げるようして、DBの負荷分散をさせる方法です。
RailsのGemでそれを、やってくれるGem octopus (Git Hub - tchandy/octopus) があるので、これを使った実装は説明します。

インストール

gemのインストールは、Gemfileに以下を追加して、bundle install
Gemfile
# DB master slave振り分け
# https://github.com/tchandy/octopus
gem 'ar-octopus'

DBの接続先の設定

接続先の設定は、config/shards.ymlに行って行きます。
公式のConfigファイルの設定に関するWikiはこちら。

全クエリの振り分け

更新のクエリは、マスターDB、参照のクエリはスレーブDBと全てのクエリに対して設定を行う場合は、下記。
octopus:
  replicated: true
  environments:
    - development
    - staging
    - production
  development:
   development_slave:
      database: development_slave_db
      adapter: postgresql
      encoding: utf8
      pool: 5
      username: postgres
      password: password
      port: 5432
      host: localhost
      timeout: 5000

  staging:
    staging_slave1:
      database: staging_slave_db
      adapter: postgresql
      encoding: utf8
      pool: 5
      username: postgres
      password: password
      port: 5432
      host: localhost
      timeout: 5000

  production:
    production_slave1:
      database: production_slave1
      adapter: postgresql
      encoding: utf8
      pool: 5
      username: postgres
      password: password
      port: 5432
      host: localhost
      timeout: 5000
    production_slave2:
      database: production_slave2
      adapter: postgresql
      encoding: utf8
      pool: 5
      username: postgres
      password: password
      port: 5432
      host: localhost
      timeout: 5000

モデル毎の振り分け

モデル毎に、マスター、スレーブを使うかを決める場合は、こちら。
fully_replicated: false を指定する。
octopus:
  replicated: true
  fully_replicated: false
  environments:
    - development
    - staging
    - production
  development:
   development_slave1:
      database: development_slave_db
      adapter: postgresql
      encoding: utf8
      pool: 5
      username: postgres
      password: password
      port: 5432
      host: localhost
      timeout: 5000

  staging:
    staging_slave1:
      database: staging_slave_db
      adapter: postgresql
      encoding: utf8
      pool: 5
      username: postgres
      password: password
      port: 5432
      host: localhost
      timeout: 5000

  production:
    production_slave1:
      database: production_slave1
      adapter: postgresql
      encoding: utf8
      pool: 5
      username: postgres
      password: password
      port: 5432
      host: localhost
      timeout: 5000
    production_slave2:
      database: production_slave2
      adapter: postgresql
      encoding: utf8
      pool: 5
      username: postgres
      password: password
      port: 5432
      host: localhost
      timeout: 5000
config/shards.ymlの他に、modeleから、replicated_model() メソッドを呼び出します。
#This class is replicated, writes to master and reads to slave.
class Cat < ActiveRecord::Base
  replicated_model()
end

接続の確認

上記まで、設定が完了していれば、rails c で
Cat.find(1)
とすれば、 下記のような、ログがとれるはずです。
[Shard: development_slave1]  Cat Load (0.4ms)  SELECT "cats".* FROM "cats" WHERE "cats"."id" = $1 LIMIT 1  [["id", 1]]

直接DBを選択する場合

usingメソッドを呼び出します。

マスターに投げる場合

Cat.using(:master).find(1)

スレーブに投げる場合

Cat.using(:development_slave1).find(1)
または、ブロックを使って。
Octopus.using(:development_slave1) do 
 Cat.count
end

テスト環境の構築

テスト環境にもマスターDB、スレーブDBを作るのが環境的にはベストかもしれませんが、状況によって作れないこともあると思うので、
そんな時は、読み取り専用のユーザーを作るの楽かと思います。

読み取り専用ユーザーの作成

postgresでの読み取り専用ロールの作り方は以下
CREATE ROLE read_only_user LOGIN REPLICATION PASSWORD 'password';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only_user;

shards.ymlの設定変更

下記のように、databaseをマスターDBに向けて、usernameをread_only_userに変更すれば、OKです。
octopus:
  replicated: true
  fully_replicated: false
  environments:
    - development
    - staging
    - production
  development:
   development_slave:
      database: development_master_db
      adapter: postgresql
      encoding: utf8
      pool: 5
      username: read_only_user
      password: password
      port: 5432
      host: localhost
      timeout: 5000

障害発生時の対応

もし、マスターDBに以上が発生した場合などに、取り合えず、振り分け処理を停止したい時は - production をコメントアウトするのが有効かと思います。
octopus:
  replicated: true
  fully_replicated: false
  environments:
#    - development
#    - staging
#    - production
  development:
   development_slave:
最低限、productionのみコメントにすれば、OKかと思います。
また、開発環境やステージング環境で、レプリケーション不要なんて時もコメントアウトしておけば、振り分け処理はおこなわれません。

2014年10月17日金曜日

ActiveRecordでhavingする方法

ActiveRecordでhavingを使いたい場合。
bookテーブルでカテゴリが同じものの、価格の合計が10000円以上のもの見たいな想定です。
Book.group('category').having('sum(price) >= 10000')
発行されるSQLは、以下のようになります。
SELECT
  book.*
FROM book
GROUP BY category
HAVING sum(price) > 10000

2014年10月9日木曜日

RailsでCSVを生成して、そのままS3にアップロードする方法

夜間バッチなどで、定期的に外部システム用のCSVをAWSのS3にアップロードすることを想定しています。

作成方法

gemのインストール

まず、gemのインストール。
Gemfileに以下を追加。
# http://aws.amazon.com/sdkforruby/
# https://github.com/aws/aws-sdk-core-ruby
gem 'aws-sdk'
GitHubaws : aws-sdk-core-rubyはこちら。
以下を実行
bundle install

Railsへの実装

前提条件として、S3にbucketが作成済みの想定です。
      require 'csv'

      begin
        users = user.all

        data = CSV.generate do |csv|

          csv << ['id', '氏名']

          users.each do |user|
            raw = []
            raw << user.id
            raw << user.name
            csv << raw
          end
        end

        AWS.config(:access_key_id => 'your-access_key_id',
                   :secret_access_key => 'your-secret_access_key',
                   :region => 'your-bucket_region')

        s3 = AWS::S3.new
        bucket = s3.buckets['your-backet']

        file = data.sjisable.encode(Encoding::SJIS, {:invalid => :replace, :undef => :replace, :replace => '?'})
        file_name = "users.csv"
        file_full_path="user_data/#{file_name}"

        object = bucket.objects[file_full_path]
        object.write(file ,:acl => :public_read)

        puts '全ての処理に成功しました。'
      rescue => e
        puts "error occurred batch : #{Time.now} : #{e.message}"
      end
上記では、全Userのid と 氏名のカラムを持ったCSVを作成して、それを、S3にアップロードすることを想定しています。
途中こちらの部分でS3へのアクセスの設定を行っています。
 AWS.config(:access_key_id => 'your-access_key_id',
                   :secret_access_key => 'your-secret_access_key',
                   :region => 'your-bucket_region')
s3 = AWS::S3.new
bucket = s3.buckets['your-backet']
下記は適宜、自分の環境にあったものを設定してください。
  • your-access_key_id
  • your-secret_access_key
  • your-bucket_region
  • your-backet
頻繁にアクセスが発生する場合は、こちらconfig/initialyzerのしたaws.rb見たいのを作ってそちらで設定してしまってもよいかもしれません。
今回は、単発のバッチということなので、つどつど設定を行っています。

2014年10月1日水曜日

Railsのcron管理 Gem wheneverの導入方法

Railsでcron管理に便利なgem wheneverの導入方法です。
公式のgitHubはこちら

gemのInstall

gemfileに以下を追加して、bundle installを実行
Gemfile
# cron
# https://github.com/javan/whenever
gem 'whenever', :require => false

設定ファイル(schedule.rb)の作成

projectのディレクトリに移動してから、設定ファイル生成コマンドを実行
$ cd /apps/[my-project]
$ wheneverize .
そうすると
./config/schedule.rb
が作成されます。

schedule.rbの作成の設定

呼び出し可能な job type

wheneverでは、下記の4つのjob typeをschedule.rbに設定することができます。
job type メソッド 使用例
bashコマンド command command “/usr/bin/some_great_command”
rakeタスク rake rake “some:great:rake:task”
Railsのメソッド runner runner “MyModel.some_method”
scriptファイル script script “script/my-script.sh”

時間の設定

3時間毎
every 3.hours do
  rake "my:rake:task"
end
毎日 AM4:30に実行
every 1.day, :at => '4:30 am' do
  rake "my:rake:task"
end
1時間毎に実行
every :hour do # Many shortcuts available: :hour, :day, :month, :year, :reboot
  runner "SomeModel.ladeeda"
end
日曜日pm12時に実行
every :sunday, :at => '12pm' do # Use any day of the week or :weekend, :weekday
  runner "Task.do_something_great"
end
27~31日の00:00に実行
every '0 0 27-31 * *' do
  command "echo 'you can use raw cron syntax too'"
end
このcron(分,時,日,月,曜日)の書き方が慣れているので普段はこれを使っています。

crontabへの反映,削除

上記で設定した内容をcrontabへ反映したり、削除させるための、下記コマンドが用意されています。

crontabへの追加

whenever -w

crontabからの削除

whenever -c

statistics