2012年12月18日火曜日

Asset Pipeline(Precompilling Assets)

特定のcssをデプロイ時にコンパイルしたい時のメモ

layoutが分かれていて以下の様になっている。
<%= stylesheet_link_tag    "top", :media => "all" %>

application.cssが以下の様に指定してある
*= require_tree ./application

このような場合トップのtop.cssをコンパイルしたいが、コンパイルされない
毎回デプロイしていては面倒なのでローカルで実験をする
  1. database.ymlにproduction環境を作成する
  2. dbを作成する(rake db:setup)
  3. コンパイルする(rake assets:precompile)
  4. public/assetsにtop.cssがない
  5. config/environments/production.rbを変更する
# config.assets.precompile += %w( search.js)
以上を以下の様に変更する
config.assets.precompile += %w( *.css *.js )

以上の内容はrailsガイドのAsset Pipelineに詳しく書いてあります。

2012年10月31日水曜日

バックアップサーバーを構築

バックアップサーバーの概要

  • 本体のサーバーからレプリケーションをする(mysql)
  • 毎日1回ダンプする
  • ダンプファイルはzipに変換し一週間分貯め古いモノから削除
  • autosshで切れても自動で接続する
  • サーバーが止まっても起動時にトンネルを自動で掘る
  • サーバーはubuntu1(マスター)とubuntu2(スレーブ)とする(両方VPSで違うサーバー)
1、sshの設定

スレーブ側からマスターへログイン出来るようにする
http://www.oiax.jp/rails3book/public_key.html(ssh公開鍵の設置方法)
スレーブ側からマスターへトンネルを貼る
autossh -M 10002 -f -N -L 23306:マスターIP:3306 ログインユーザー@マスターIP
(autosshは接続が切れた場合自動でつなげてくれるもの)
mysql -h 0.0.0.0 -P 23306 -u root(マスターのmysqlにログイン)
エラー発生
Lost connection to MySQL server at 'reading initial communication packet', system error: 0
my.confに以下を記入
bind-address            = 0.0.0.0

2,レプリケーションの設定(マスター)

my.confの設定
vi /etc/mysql/my.cnf
server-id = 1
log-bin = mysql-bin
replユーザーの作成
GRANT REPLICATION SLAVE ON *.* TO repl@% IDENTIFIED BY '';
テーブルをロックして実行
>FLUSH TABLES WITH READ LOCK;
>SHOW MASTER STATUS;
+-----------------------+----------+------------------+----------------------+
 | File                             | Position| Binlog_Do_DB| Binlog_Ignore_DB |
+-----------------------+----------+------------------+----------------------+
 | mysql-bin.000003 |      256   |                             |                                   |
+-----------------------+----------+------------------+----------------------+
1 row in set (0.00 sec)
テーブルロック解除
UNLOCK TABLES;

3,レプリケーションの設定(スレーブ)

my.confの設定
vi /etc/mysql/my.cnf
server-id = 2(マスターと違う数字)
log-bin = mysql-bin
レプリケーション先指定
CHANGE MASTER TO
MASTER_HOST='127.0.0.1',
MASTER_PORT=23306,
MASTER_USER='repl',
MASTER_PASSWORD='',
MASTER_LOG_FILE='mysql-bin.000003',
MASTER_LOG_POS=256;
先ほどのステータスで確認したファイルとポジションを記入
レプリケーションのステータス確認
SHOW SLAVE STATUS\G
ここが
Slave_IO_RunningがYes
Slave_SQL_RunningがYes
となればOK
レプリケーションの開始
START SLAVE;
何か一つ増やして中身をお互い確認し変更が同じであればOK

4,システム起動時にトンネルを掘る

/etc/rc.localに追記すればシステムが起動した際に実行してくれる
先ほどのautosshのコマンドをフルパスで、他ユーザーを指定するならば
sudo -u ユーザー名を追加

5,cronスクリプトでダンプを行う設定


SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
#MAILTO=root
MAILTO=""
HOME=/

# run-parts
0 4 * * * root/シェルファイル名.sh

上から5行を追加することでメールを送る必要がなくなる。
(最初にpostfixがインストールされていない為に実行されなかったので追記)
この内容であれば毎日午前4時にシェルが実行される

6、実行シェルの記述
以下を参考に。
http://d.hatena.ne.jp/alexam/20120609/1339228291

以上。結構こういうの頻繁に使いそうな予感・・・

2012年10月24日水曜日

画像認証(simple_capcha)を使ってメール送信

今回は問い合わせフォームを作成する際に画像認証が必要だったので勉強しました。
またメールを送る際にデータベースと関わりがない「Active model」を使用してみました。

まずはGemを使います。(Gemfile)
gem "galetahub-simple_captcha", :require => "simple_captcha"
Gemの情報はこちらhttps://github.com/galetahub/simple-captcha
次にsimple_captchaのdbを作成します。
$ rails g simple_captcha
$ rake db:migrate
インストールした後サーバーを再起動しこっからじゃんじゃん作ります。
ルーティング〜(config/routes.rb)
resource :message, only: [ :new, :create ] do
  get :thanks
end
次はモデル〜(models/message.rb)
class Message
  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend ActiveModel::Naming
  extend SimpleCaptcha::ModelHelpers::SingletonMethods

  apply_simple_captcha
  attr_accessor :body, :name, :email, :subject

  validates :body, :subject, :name, presence: true
  validates :email, format: { with: /^[0-9a-zA-Z_\.\-]+?@[0-9A-Za-z_\.\-]+\.[0-9A-Za-z_\.\-]+$/, allow_blank: true }, presence: true

  def initialize(attributes = {})
    attributes.each do |name, value|
      send("#{name}=", value)
    end
  end
 
  def persisted?
    false
  end
end
「extend SimpleCaptcha::ModelHelpers::SingletonMethods」と「apply_simple_captcha」が重要です!
Active Modelの場合はいろいろと書く必要があります。
  • include ActiveModel::Validation
  • include ActiveModel::Conversion
  • extend ActiveModel::Naming
この3つはデフォルトで付けてもいいかもしれません。
あとはメールのバリデーションを行い、下に記述した
  • initialize
  • persisted?
もデフォルトで良いかと思います。

次はコントローラーを作成します。(controllers/messages_controller.rb)
class MessagesController < ApplicationController
  def new
  end
 
  def create
    @message = Message.new(params[:message])
    if @message.valid_with_captcha?
      MessageMailer.standard(@message).deliver
      redirect_to action: :thanks
    else
      flash.now.alert = "記入内容に不備があります。"
      render :new
    end
  end
 
  def thanks
  end
end
アプリケーションコントローラーにも追加(controllers/application_controller.rb)
include SimpleCaptcha::ControllerHelpers
ここはメール送るコントローラーとあまり変わりませんが、
「valid_with_captcha?」で画像認証の判断をしています。
やっとviewです。newを作成します。(messages/new.html.slim)
div class="message_views"
  h2 お問い合わせ
  div class="messages"
    div class="form"
      = form_for @message, url: message_path do |f|
        div class="form_column
          = f.label :name, "名前"
          = f.text_field :name
        div class="form_column
          = f.label :email, "メールアドレス"
          = f.text_field :email
        div class="form_column
          = f.label :subject, "件名"
          = f.text_field :subject
        div class="form_column"
          = f.label :body, "本文"
          = f.text_area :body
        div class="form_column_captcha"
          = f.simple_captcha label: "Enter ..."
        div class="submit"
          = f.submit "送信"
ここで重要なのは「f.simple_captcha label: "Enter ..."」ですね。Enterの所に好きな文言を入れればそれっぽくなります。
次はメールの文章内容です。(message_mailer/standard.text.erb)
○ wataruからのメッセージ受信のお知らせ
--------------------------------------------------------
<%= @message.name %>さんからメッセージが届いています。
送信元:<%= @message.email %>
--メッセージ--

<%= (@admin_message.body) %>

+………………………………………………………………………+
  メモってくれちゃってホントね!
+………………………………………………………………………+
最後はメーラーを作って終わりです。(mailers/message_mailer.rb)
class MessageMailer < ActionMailer::Base
  default charset: 'ISO-2022-JP', from: "info@example.com"

  def standard(message)
    @message = message
    mail(to: "wataru@example.jp", subject: message.subject)
  end
end

Ajaxでチェックボックスの値を保存

よくあるチェックボックスにチェックをon/offするだけで保存される仕組みをAjaxで作成しました。

@advertisements.each do |ad|
 = form_for ad, url: update_display_path(id: ad.id), remote: true do |f|
  = f.label :display, "on/off"
  = f.radio_button :display, true, { checked: ad.display }
的なフォームを作って
$(function(){
  $('#advertisement_display_true').click(function(){
    var val = $(this).val();
    var target = $(this.form).attr("action");
    $.ajax({
      type: "PUT",
      url: target,
      data: val,
      success: function(){
        alert("OK")
      }
    });
  });
});
とすればOK
流れは・・・
radio_buttonをクリックしたら、

そのvalueと

そのフォームのアクションを取ってきて

ajax内のtypeをput,

urlをアクション、

送るdataをvalueで、

成功時にOKっというメッセージを出す
という流れです。
Ajaxは利用者ががんがんスムーズに作れるので嬉しいですよね〜

2012年10月10日水曜日

RailsとMemocached

今回の案件で、
「同じIPアドレスから10分以内にアクセスが来たら、アクセスカウンターに加算しない」
という内容があったので、これをmemcachedを使って再現してみました。

まずはインストールその後Gemを入れます。
$ apt-get install memcached

https://github.com/mperham/dalli
gem "dalli" # memcached client

$ bundle install
次は設定です。キャッシュは10分で消すように設定します。
environments/development
MEMCACHED = Dalli::Client.new("localhost:11211", :expires_in => 600 )
あとはコントローラで以下の様に設定するだけだけ。
以下はコントローラーのプライベートで作りました。
「User」というテーブルに「dl_count」があってこれがアクセスカウンターです。
def counter
  key = "#{@user.id}-#{request.remote_ip}"
  unless MEMCACHED.get(key)
    @user.update_attribute(:dl_count, @user.dl_count + 1)
    MEMCACHED.set(key, true)
  end
end
あとは必要な所に上のメソッドを追加するだけ。
簡単でしたね〜

キャッシュの消し方も必要ですね。
コンソールで以下を実行します。
$ rails c
> dc = MEMCACHED
> dc.flush_all
以上でごあす!

postfixとrails

初めてこのサーバーでメールを送ろうと思った際に簡単なセットアップだけメモ

まずはサーバーでインストールをします。

# apt-get install -y postfix

よく分からないからとりあえず再スタート(動いているかの確認)
# /etc/init.d/postfix restart
Stopping Postfix Mail Transport Agent: postfix.
Starting Postfix Mail Transport Agent: postfix.
次にテスト〜
$ mailx memo@example.com
subject: test
test
cc:
シフトDで終了
とりあえずサーバーではこれだけにして、
 Railsで設定を以下のようにする。

enviroments/production
config.action_mailer.default_url_options = { :host => "example.com" }
config.action_mailer.smtp_settings = { :enable_starttls_auto => false }

developementでもテストしたい場合は同じようにdevelopmentにも設定。
ログで確認をする。

以上でとりあえずメールが届きました〜

2012年7月30日月曜日

メールを送る

今回はメールを送る方法を紹介します。

Gemを追加します。
gem 'delayed_job'
gem 'delayed_job_active_record'
gem 'daemons'
選び方などは省略するとして、モデルでdeliverを作成します。
has_many :destinations, through: :parcels, class_name: "User"

def destination_id_map=(hash)
  hash.each do |user_id, value|
    c = User.find_by_id(user_id)
    self.destinations << c
  end
end

LIMIT = 100

def deliver
  return unless status == "wating"

  log_dir = Rails.root.join('log', 'messages')
  Dir.mkdir(log_dir) unless File.exist?(log_dir)
  filename = sprintf('%06d.log', id)
  logger = Logger.new(Rails.root.join('log', 'messages', filename))

  logger.info('started. ' + Time.current.to_s(:db))
  current_parcels_id = nil
  loop do
    parcels = self.parcels.order('parcels.destination_id ASC').limit(LIMIT).includes(:destination)
    parcels = parcels.where('parcels.id > ?', current_parcels_id) if current_parcel_id
    parcels.each do |parcel|
      next if parcel.destination.deleted?

      begin
        UserMailer.notice(parcel).deliver
        parcel.update_attribute(:status, "sent")
        logger.info(sprintf("06d ok %s %s", parcel.destination_id, parcel.destination_mail))
      rescue Exception => e
        raise e if Rails.env.test?
        parcel.update_attribute(:status "error")
        logger.info(sprintf("%06d NG %s %s", parcel.destination_id, parcel.destination_mail e.to_s))
      end
    end
    break if parcels.size < LIMIT
    current_parcel_id = parcels.last.id
  end
  update_attribute(:status, "sent")
  logger.info('Ended. ' + Time.current.to_s(:db))
end
mailer/user_mailer.rbを作成します
class UserMailer < ActionMailer::Base
  default from: "from@example.com"

  def notice(parcel)
    mail(
      :subject => parcel.message.subject,
      :from => parcel.message.from,
      :to => parcel.destination.mail
    )
  end
end
controllerのcreateで作成して完了です。
def create
  @message = Message.new(params[:message])
  if params[:destinations].kind_of?(Hash)
    @message.destination_id_map = params[:destinations]
    @message.save!
    @message.delay.deliver
    redirect_to action: :index
  end
end
delayed_jobは使い方を学ぶ必要があります。Google先生に聞いて下さい。

ログイン時にユーザー名を保存

ログインでは次回から自動ログインはたくさん見かけますが、今回はクッキーを使った、名前を保存を作成します。

まずはチェックボックスを作成します。
<%= check_box_tag :remember_me, cookies[:user_name].present? %><%= label_tag :remember_me, 'ユーザー名を保存' %>
つぎにコントローラーの設定を変えます。
def create
  admin = Administrator.find_by_user_name(params[:user_name]
  if params[:remember_me] && params[:user_name].present?
    cookies.permanent[:user_name] = params[:user_name]
  else
    cookies.delete(:user_name)
  end
  -省略-
end
という形にします。
もしパラメーターにremember_meが来てて、user_nameが空ではない場合、
cookiesにpermanent(永遠)に保存するということです。
今回はユーザー名だけなのでこれで問題ないと思います。

最後にログインフォームに加えます。
<%= text_field_tag :user_name, cookies[:user_name] %>
これでクッキーが入ります。
こう見ると簡単でしたね〜

2012年7月26日木曜日

Ajax:保存orエラーを返す

今回はフォームの入力欄で入力が完了したら自動で(Ajax)で保存を行う。
ただしすでにあるモノを更新するというテイで!


controller
  def index    
  @users = User.order('id ASC')
  end

  # Ajax
  def update
    @user = User.find(params[:id])
    @user.update_attributes params[:user]
    @users = User.order('id ASC')
    render :index
  end

次にviewのindexです。
<div class="success"><p></p></div>
<% @users.each do |c| %>
   <tbody>
      <tr>
        <%= form_for
           (c, url: { action: :update, id: c.id }, 
            html: { id: "user_#{c.id}" }
           ) do |form| 
        %>
        <td><%= c.name %></td>
        <td>
           <%= form.text_field :display_name, class: "edit_display_name" %>
        </td>
        <td>
          <%= form.check_box :shown, { class: "shown" }, true, false  %>
        </td>
        <% end %>
      </tr>
   </tbody>
<% end %>
これで1つの箱の中にいくつものformがある設定ができました。
次にAjaxです。

assets/javascript内に自由にファイルを作成します。
$(function(){   $(".edit_display_name").change(function() {
    var val = $(this).val();
    var val2 = $(this).parent().next().children("input").val();
    var target = $(this.form).attr("action");
    if (val == "") {
      alert("表示名を入力してください。");
      $(this).css("background-color","yellow");
      return
    }
    $.ajax({
      type: "PUT",
      url: target,
      data: {
        user: {
          display_name: val,
          shown: val2
        }
      },
      success: function() {
        $("div.success").show()
        $("div.success p").html("「表示名」を" + val + "に変更しました。");
      }
    })
  })


  • val,val2でformのvalueを取ってきて、targetのactionを指定しています。
  • それをdataに入れて、urlにtargetを入れます。
  • updateなのでもちろんtypeはPUTです。
  • その前にエラーの場合、今回はvalが空の場合はalertを出し、returnしています。
  • エラー時にはcssでフォームを黄色にしています。
  • もし問題なく進めば、successが呼ばれて、メッセージが呼ばれます。
  • div.successはdisplay:noneに指定しておけば以上のsuccessでOKです。

Rails:簡単戻るボタン

簡単な戻るボタンを作成

<%= link_to '戻る', :back %>

これはhistory.back()と同じです。 これでOKなんですけど、前のURLによって変えたい時は

<% if request.referer.include?("?") %>

とすると検索されていた場合というif文が作成できる


以上です。

jQuery:全てにチェックを付ける!

今回はよくある全てにチェックを付けるという内容です

参考までに


assets/javascript内に新しいファイルを作成します。
$(function() {
  $("#全てにチェックのcheck_boxのID名").click(function() {
    if(this.checked) {
     $(".チェックボックスのクラス名").attr('checked', $(this).attr('checked'));
    }else{
     $(".チェックボックスのクラス名").removeAttr('checked')
    }
  });
});
ここで重要なのはチェックボックスのクラス名で、クラスにすること。基本IDは1つなので当たり前か・・・ これだけでできあがり!

nginxでrailsのpublic内を取得する方法

ローカルではできたのに、サーバーに上げたら見えなくなったのでメモ


root /var/www/hoge/current/public;

---省略---

 if (!-f $request_filename) {
    proxy_pass http://hoge_server;
}


重要なのは下の部分を追加するということ!

2012年7月9日月曜日

Ubuntu12.04ーランチャーの項目削除!

 クラッシックタイプで上のランチャーの項目を削除したい場合の方法は・・・

Alt + 右クリック!!

2012年7月5日木曜日

文字列の最大値を繰り上げ

数字なら分かるのですが、文字列の場合繰り上げするのは・・・
と思っていたらありました便利なモノが!それは「succ」

before_save do
  last_number#before_saveで登録してます
end

def last_number
    if new_record?#新規登録時?
      if number.blank?#numberカラムは空?
        last_column = User.find(:all).max#ユーザーテーブル全ての最大値だけを探します。
        if last_column
          self.number = last_column.number.succ#numberカラムの最大値を1つ繰り上げします。
(AAAAならAAAB,1なら2というように)
        else
          self.number = "1"#一番最初の登録で記入がないなら1を入れます。
        end
      end
    end
  end

CSVのアップロード

業務系のシステム開発であれば必要となることが多いCSVのアップロード。
それを作ってみました!
環境は
Ruby 1.9.3
Rails 3.2.5
DB mysql2

さぁ作り始めます!
まずはCSVを作成します。
できればテストでも使いたいので"spec"下に"data"フォルダでも作って置いておきましょう!

モデルから作ります。
cattr_accessor :rejected_rows, instance_reader: false
@@rejected_rows = []#エラーメッセージを表示します。

 class << self
    def create_from_csv_data(data)
      self.rejected_rows.clear#エラーメッセージをクリアにします。
      data = NKF::nkf('-S -w', data)
      csv = CSV.new(data)
      inserted = 0#新規登録件数
      updated = 0#更新件数
      transaction do
        csv.each_with_index do |arr, idx|
          next if arr[0] == "登録日時" #CSVの一行目の一つ目が登録日時であってはなりません
          next if arr.all? { |value| value.empty? } #valueが空は飛ばします。
          user = find_by_number(arr[1]) #userのnumberカラムを探します。
          already_number = ( arr[1] != User.find_by_number(arr[1]))#既に登録されているかどうか調べます。
          #登録済みのユーザー番号の場合
          if user#userのユーザー番号が存在した場合
            user.assign_attributes(values_for(arr), without_protection: true)#更新します。
            if user.changed?#変更されている場合はsaveします。
              if user.save
                updated += 1#更新件数に1追加します。
              else
                self.rejected_rows << [ idx, user ]#エラーの場合は行数とエラーメッセージを挿入します。
              end
            end
            #ユーザー番号が空もしくは登録したいユーザー番号が入っている場合
          elsif already_number || arr[1].blank?
            user = self.new(values_for(arr), without_protection: true)#新規登録します。
            if user.save
              inserted += 1#新規登録件数に追加します。
            else
              self.rejected_rows << [ idx, user ]#エラーメッセージを入れます。
            end
          else
          end
        end

        raise ActiveRecord::Rollback if rejected_rows.present?#エラーの場合ロールバックしてエラーメッセージを返します。
      end
      return inserted, updated#無事登録出来た場合は件数を返します。
    end
 
    private
    def values_for(arr)#ユーザーの内容をここに追加します。(privateにしないと長いので)
      {
        number: arr[1],#ユーザー番号
        family_name: arr[2],#名字
        kana_family_name: arr[3],#名字フリガナ
        given_name: arr[4],#名前
        kana_given_name: arr[5],#名前フリガナ
        sex: arr[6],#性別
        birth: arr[7],#誕生日
        email: arr[8],#メール
        zip_code: arr[9],#郵便番号
        prefecture: arr[10],#都道府県
        address1: arr[11],#住所
        address2: arr[12],#住所2
        phone: arr[13]#電話番号
      }
    end
  end
次にルーティングを指定します。
get 'users/import', controller: 'users', action: 'import'
post 'users/csv_import', controller: 'users', action: "csv_import"
次にコントローラーです。
def import
   User.rejected_rows.clear#エラーをクリアします。
end

def csv_import
  if params[:file].present?
    begin#トランザクション開始です。
      inserted, updated = User.create_from_csv_data(params[:file].read)#先ほど作りましたメソッドです。
      if User.rejected_rows.empty?#エラーがないなら?
        flash[:notice] = I18n.t(:notice, scope: [ :users, :csv_import], inserted: inserted, updated: updated)#i18nを使って更新と新規登録の文章をflashで出しています。
        redirect_to action: :import
      else
        render 'import'
      end
    rescue => e
      flash.now.alert = e.message#エラーメッセージを出しています。
      render 'import'
    end
  else
    flash.now.alert = "ファイルを選択して下さい。"
    render 'import'
  end
end
やっとビューにいけました
#フォームです。ここは簡単ですね。
<%= form_tag([:users, :csv_import], :multipart => true) do %>
  <table>
    <th>
      <%= label_tag :file, t('.file') %>
    </th>
    <td>
      <%= file_field_tag(:file) %>
    </td>
  </table>
  <div class="submit">
    <%= submit_tag t('.upload') %>
  </div>
<% end %>
#以下はエラー文
<% if User.rejected_rows.present? %>
<p class="alert">CSVファイルに以下の問題が見つかりました。修正して再びアップロードしてください。</p>

<table class="errors">
    <tr>
      <th>行番号</th>
      <th>エラーの説明</th>
    </tr>
    <% User.rejected_rows.each do |row| %>
      <% idx = row[0]; user = row[1] %>
      <tr>
        <td><%= idx + 1 %></td>
        <td class="error_content">
          <ul>
            <% user.errors.full_messages.each do |msg| %>
              <li><%= msg %></li>
            <% end %>
          </ul>
        </td>
      </tr>
    <% end %>
  </table>
<% end %>

なるべくコントローラーを軽くして動きが分かりやすいようにします。
モデルで書けるものはモデルに書くことをオススメします。

Ubuntu-ATOK3の枠を消す方法

ATOKを入れて目障りな左下の枠を消す方法!
# cd /etc/X11/xinit/xinput.d
# vi iiimf
その中の一番下に
/opt/atokx3/sample/iiimf_status_hide
を記入する よって以下のようになる
XIM=iiimx
XIM_PROGRAM=/usr/bin/iiimx
XIM_ARGS=-iiimd
GTK_IM_MODULE=iiim
QT_IM_MODULE=xim

if [ "$lang_region" = "ja_JP" ] ; then
  export HTT_DISABLE_STATUS_WINDOW=t
  export HTT_GENERATES_KANAKEY=t
  export HTT_USES_LINUX_XKEYSYM=t
  export HTT_IGNORES_LOCK_MASK=t
  export JS_FEEDBACK_CONVERT=t
fi

/opt/atokx3/sample/iiimf_status_hide
これで僕は解決されました!

2012年5月2日水曜日

API:jsonをテストする〜Rspec〜

前に作ったAPIのテストを作ってなかったのでそこのテストを作ります。
テストはRspecを使うのでまずは「test」フォルダを削除します。
次にGemfileを変更します。

Gemfile
group :development, :test do
  gem 'thin', :platforms => :ruby
  gem 'annotate', :git => 'git://github.com/ctran/annotate_models.git'

  gem 'rspec-rails'
  gem 'launchy'
  gem 'capybara'
  gem 'database_cleaner'
  gem 'factory_girl_rails', '~> 1.7.0'
end

thinはwindowsだと動かないのでdevelopmentだけにしました。
annotateはmodelにdbの内容を書き出してくれる便利なやつです。
さぁこれで準備OK!あとはコマンドでRspecの準備をします。

$ bundle install
もともとインストールあるので今回はやってませんが通常は以下の手順で行います。
$ gem install rspec
$ rails g rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb

これでrspecフォルダが出来ました。 次にspecフォルダの下に「support」フォルダを作成し「integrration_test_helper.rb」を作成し以下の内容にします。
module IntegrationTestHelper
  def debug
    save_and_open_page
  end

  def dump
    puts page.body
  end
end
まずはテスト用データベースを作成します。
FactoryGirl.define do
  factory :announcement do
    sequence(:subject) { |n| "タイトル%02d" % n }
    sequence(:content) { |n| "お知らせ本文内容%02d" % n }
  end
end
作成方法は「FactoryGirl」で行います。spec下に「factories」フォルダを作成します。
そしてその下に「announcements.rb」を以下の様に作成します。

今回テストするのはコントローラーなので「controllers/api」フォルダを作ります。
そして中にannouncements_controller_spec.rbを以下の様に作成します。
# encoding: utf-8

require 'spec_helper'

describe Api::AnnouncementsController do
  render_views

  include IntegrationTestHelper #テストヘルパーを呼んでいます
  let(:announcement) { FactoryGirl.create(:announcement) }
   # FactoryGirlの情報をここに持ってきます。

  before do
    announcement
  end

  describe "#index (json)" do
    it "jsonデータを返す" do
      get :index, :format => "json"
      arr = JSON.parse(response.body)
    # 一度arrに入れてruby型式にしてjsonは配列で、かつ配列が1つだけなので
    [0]としてshouldで表示させます。

      arr[0]["id"].should == announcement.id
      arr[0]["subject"].should == announcement.subject
      arr[0]["content"].should == announcement.content
    end
  end
end
これを通したいと思います。
$ rake db:test:prepare
$ rake spec

~/.rvm/rubies/ruby-1.9.3-p0/bin/ruby -S rspec ./spec/controllers/api/announcements_controller_spec.rb
.

Finished in 0.25023 seconds
1 example, 0 failures

無事に通りました。「arr」に一度「JSON.parse(responce.body)」で代入することが重要ポイントの様ですね。

APNs:APNsの実装-Ruby on Rails

今回APNsを実装するサービスは初めてだったので四苦八苦しました。
以下のサイトを参考にして構築しました。
【iPhone】Push Notificationの実装方法

作成するのはモデルです。
notification_provider.rbを作成します。
# coding: ascii-8bit

require "openssl"
require "socket"

class NotificationProvider
  include ActionView::Helpers::JavaScriptHelper

  HOST = case Rails.env
    # when "test"        then "gateway.sandbox.push.apple.com"
    when "development" then "gateway.sandbox.push.apple.com"
    when "production"  then "gateway.push.apple.com"
    end
  PORT = 2195

  CERT_FILE = Rails.root.join("config/鍵名.cert")
  RSA_KEY_FILE = Rails.root.join("config/鍵名.key")

  attr_reader :ssl
以上の様にします。
「CERT_FILE」と「RSA_KEY_FILE」が出てきたのでこちらを作成します。
まずは作成した鍵(pem)を/configの直下に置きます。
そしてその鍵内上の
「-----BEGIN CERTIFICATE-----」から「-----END CERTIFICATE-----」を全て
をコピーして「〜.cert」として作りpem鍵と同じ場所に置きます。
同じくその下の
「-----BEGIN RSA PRIVATE KEY-----」から「-----END RSA PRIVATE KEY-----」
を全てコピーして「〜.key」として作りpem鍵と同じ場所に置きます。
これで準備OK!さぁ作りましょう〜

続いて同じnotification_provider.rbに追記します。
class << self
    def open
      provider = self.new
      provider.open
      provider
    end
  end

  def open
    return if @ssl
    if HOST
      @socket = TCPSocket.new(HOST, PORT)
      context = OpenSSL::SSL::SSLContext.new("SSLv3")
      context.cert = OpenSSL::X509::Certificate.new(File.read(CERT_FILE))
      context.key  = OpenSSL::PKey::RSA.new(File.read(RSA_KEY_FILE))
      @ssl = OpenSSL::SSL::SSLSocket.new(@socket, context)
      @ssl.connect
    else
      @ssl = DummyServer.new
    end
  end

  def close
    @ssl.close if @ssl
    @socket.close if @socket
    @ssl = @socket = nil
  end

  def closed?
    @ssl ? @ssl.closed? : true
  end

  def message(identifier, expired_at, token, alert)
    alert = j(alert.dup)
    alert.force_encoding("ascii-8bit") if RUBY_VERSION.to_f >= 1.9
    payload = %Q!{ "aps":{ "alert":"#{alert}", "sound":"default" } }!
 
    "\x01" +                       # 常に1
    [identifier].pack("N") +       # 識別子、4バイト
    [expired_at.to_i].pack("N") +  # 期限、4バイト(big endian)
    "\x00\x20" +                   # トークン長、2バイト、常に32
    token +                        # デバイストークン
    [payload.size].pack("n") +     # ペイロード長、2バイト
    payload                        # ペイロード
  end

  def notify(device_token, content,
        expired_at = 1.days.from_now, logger = Rails.logger)
    raise "Provider is not opened!" unless @ssl
 
    request = message(0, expired_at, device_token.token, content)
 
    response = nil
    @ssl.write(request)
    if @ssl.kind_of? OpenSSL::SSL::SSLSocket
      if IO.select([@ssl], nil, nil, 0.5)
        response = @ssl.read(6)
      end
    else
      response = @ssl.read(6)
    end
 
    if response.present?
      arr = response.unpack("ccN") # 1バイト目:8、2バイト目:エラーコード
      if arr[0] == 8
        logger.info("error code: #{arr[1]}, divece token id: #{device_token.id}")
      else
        logger.info("unknown error, divece token id: #{device_token.id}")
      end
      return false
    end
    true
  end
これはもう定型文でいいかもしれません。
さぁあとはcronスクリプトを作成する必要がある。
とりあえずcronスクリプトは置いておいてcronスクリプトを動かすための構築をします。
今回はお知らせを表示するので「announcemnet」を編集します。
def display_status
    { nil => "未送信", "delivered" => "送信済み", "delivering" => "送信中" }[status]
  end

  def not_delivered?
    status.nil?
  end

  def delivered?
    status == "delivered"
  end

  def delivering?
    status == "delivering"
  end

  def update_directly(updates)
    assign_attributes(updates, :without_protection => true)
    Announcement.update_all(updates, :id => self.id)
  end

  def deliver
    return unless not_delivered?
    update_directly(:status => "delivering")
 
    provider = NotificationProvider.open
    success = 0
    failure = 0
    total = DeviceToken.count
    DeviceToken.all.each do |dt|
      if provider.notify(dt, self.content, どれくらい送り続けるかの日数, self)
        success += 1
      else
        failure += 1
      end
      if provider.closed?
        info("APN Server closed unexpectedly!")
        break
      end
    end
    provider.close
    info("total: #{total}, success: #{success}, failure: #{failure}.")
    update_directly(:log => self.log, :status => "delivered")
  end

  def info(text)
    self.log = "" if log.blank?
    self.log += Time.now.strftime("%Y-%m-%d %H:%M:%S ") + text + "\n"
    # cronログを作成している
  end

  class << self
    def deliver_all
      DeviceToken.where("updated_at < ?", デバイストークンの有効期限).delete_all
     # デバイストークンをupdateしていない場合削除される

      announcements = active.not_delivered.past
      count = announcements.count
      announcements.each do |ann|
        ann.deliver
      end
      Rails.logger.info "executed delivered_all, #{count}"
    end
  end
以上の内容でお知らせをAPNsを使ってiPhoneやiPadでのアプリに表示させることが出来る。
ほぼ定型文で保存かな?

APNs:デバイストークンを受け取る

Apple Push Notification Service を今回実装したのでそれのメモです。
iPhoneのアプリ内でalertの様に出るお知らせです。

まずは送る相手をDBに保存するためにデバイストークンを保存します。
マイグレーションの作成です。

~_create_device_tokens.rb
class CreateDeviceTokens < ActiveRecord::Migration
  def up
    create_table :device_tokens do |t|
      t.timestamps
    end
    ActiveRecord::Base.connection.execute(
      "ALTER TABLE device_tokens ADD token VARBINARY(32) NOT NULL;")
  end

  def down
    drop_table :device_tokens
  end
end
device_tokensという名前にしました。

次にルーティングです。端末側へ情報を出すものは全て「namespace」で分けた
「api」に指定します。
routes.rbに以下を追加します。
if Rails.env.development?
   resources :device_tokens, :only => [ :new, :create ]
else
   resources :device_tokens, :only => [ :create ]
end
開発と本番で分けました。
あとはアプリ側からこちらに向けたデバイストークンを拾うだけです。

api/device_tokens_controller.rbを作成します。
class Api::DeviceTokensController < ApplicationController
  protect_from_forgery :except => :create

  def create
    @dt = DeviceToken.find_or_new(params[:token])
    if @dt.save
      render :text => "OK", :content_type => "text/plain"
    else
      render :text => "NG", :content_type => "text/plain",
        :status => 400
    end
  end
end
そして最後にモデルです。
device_token.rbを作成します。
class DeviceToken < ActiveRecord::Base
  validates :token, :presence => true, :length => { :is => 32 }

  class << self
    def find_or_new(token)
      token.force_encoding("ascii-8bit") if RUBY_VERSION.to_f >= 1.9
      dt = find_by_token(token) || new(:token => token)
      dt.updated_at = Time.current
      dt
    end
  end
end
Rubyのバージョンを1.9の時は「force_encoding」を「ascii-8bit」としています。
以上で「post /api/device_tokens?token=〜」というURLで取得できます。

2012年4月27日金曜日

配列をAPIで表示して、モデルでクラス分けする(上級編)

最後に残ったballsの配列を表示します。
これは少しややこしいです。
ball.rbを作成します。
class Ball
  attr_accessor :change, :level

  def initialize(hash)
    @change = hash[:change]
    @level = hash[:level]
  end
end
/model/user.rbのbefore_saveに追加します。
self.balls = balls.map do |ball|
  h = {}
    [ :change, :level ].each do |key|
     h[key] = ball.send(key).to_i
  end
  h
end
/mode/user.rbに作成します。
def balls
    case self[:balls]
    when String
      YAML.load(self[:balls]).map { |e| Ball.new(e) }
    when Array
      self[:balls].map { |e| e.kind_of?(Ball) ? e : Ball.new(e) }
    end
  end

  def balls=(value)
    case value
    when Hash
      self[:balls] = value.keys.sort.map do |key|
        h = {}
        value[key].each do |k, v|
          h[k.to_sym] = v
        end
        h
      end
    when Array
      self[:balls] = value
    end
  end
xmlに追加します。
xml.balls do
   balls.each do |b|
         xml.ball do
         xml.change b.change
         xml.level b.level
      end
   end
end
jsonに追加します。
:balls => u.balls
これで問題なく全ての情報が表示されました〜

配列をAPIで表示し、モデルでクラス分けする

今度は配列を表示する為にモデル内でクラスを作成して表示させます。 catchとhitはほぼ同じ内容だったので同時に行っていきます。 catch.rbを作成します。
class Catch
  attr_accessor :value
 
  def initialize(value)
    @value = value
  end
 
  def to_i
    @value.to_i
  end
end
model/user.rbに追加します。
before_save do
    self[:catches] = catches.map do |catch|
      catch.to_i
    end
  end

def catches
    case self[:catches]
    when String
      YAML.load(self[:catches]).map { |e| Catch.new(e) }
    when Array
      self[:catches].map { |e| e.kind_of?(Catch) ? e : Catch.new(e) }
    else
      (0..5).map { |n| Catch.new(n * 100) }
    end
  end
 
  def catches=(hash_or_array)
    case hash_or_array
    when Hash
      self[:catches] = hash_or_array.keys.sort.map do |key|
        Catch.new(hash_or_array[key])
      end
    when Array
      self[:catches] = hash_or_array.map { |e| Catch.new(e) }
    end
  end
xml内に追加します。
model/user.rb
xml.catches do
   catches.each do |c|
        xml.catches c.to_i
   end
end
group.rbに追加します(as_json_with_image)
:catches => u.catches,
これでcatchはxmlでもjsonでも配列が表示されました。
次は同じようにhitsもやってみましょう。 his.rbを作成します。
class Hit
  attr_accessor :value
 
  def initialize(value)
    @value = value
  end
 
  def to_i
    @value.to_i
  end
end
model/user.rbに追加します。
self[:hits] = hits.map do |hit|
      hit.to_i
    end
def hits
    case self[:hits]
    when String
      YAML.load(self[:hits]).map { |e| Hit.new(e) }
    when Array
      self[:hits].map { |e| e.kind_of?(Hit) ? e : Hit.new(e) }
    else
      (0..2).map { |n| Hit.new(n * 100) }
    end
  end
 
  def catches=(hash_or_array)
    case hash_or_array
    when Hash
      self[:hits] = hash_or_array.keys.sort.map do |key|
        Hit.new(hash_or_array[key])
      end
    when Array
      self[:hits] = hash_or_array.map { |e| Hit.new(e) }
    end
  end
xml内に追加します。
model/user.rb
xml.hits do
   hits.each do |h|
      xml.hits h.to_i
   end
end
group.rbに追加します(as_json_with_image)
:catches => u.catches,
以上で配列の2つは表示されました。

1対多をAPIで出力する

前回のGroupにUserが属している内容を表示させる。
$ rails g controller users index
      create  app/controllers/users_controller.rb
       route  get "users/index"
      invoke  erb
      create    app/views/users
      create    app/views/users/index.html.erb
      invoke  test_unit
      create    test/functional/users_controller_test.rb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke    test_unit
      create      test/unit/helpers/users_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.js.coffee
      invoke    scss
      create      app/assets/stylesheets/users.css.scss
モデルも一緒に作ります。
$ rails g model user
      invoke  active_record
      create    db/migrate/20120427062021_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/unit/user_test.rb
      create      test/fixtures/users.yml
model/user.rbを変更します。
class User < ActiveRecord::Base
  belongs_to :groups
  validates :name, :presence => true

  attr_accessible :name, :email, :email,
    :sex, :country, :area, :address1,
    :address2, :phone, :hits, :catches, :balls, :group
end
model/group.rbに追加
has_many :users, :dependent => :destroy
マイグレーションを作成
class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.references :group, :null => false
      t.string :name
      t.string :email
      t.string :sex
      t.string :country
      t.string :area
      t.string :address1
      t.string :address2
      t.string :phone
      t.text :hits
      t.text :catches
      t.text :balls

      t.timestamps
    end
    add_index :users, :group_id
  end
end
じゃあ入れましょう。
$ rake db:migrate
development/groups.rbに追加
# coding: utf-8
group = Group.create!(
  :name => "グループ1"
)

hits = [ 100, 200, 300 ]

catches = [ 10, 20, 30, 40, 50, 60 ]
balls = [
  { :change => 1, :level => 10 },
  { :change => 2, :level => 20 },
  { :change => 3, :level => 30 },
  { :change => 4, :level => 40 },
  { :change => 5, :level => 50 },
]

10.times do |n|
  User.create!(
    :name => "名前#{n}",
    :sex => "男",
    :country => "東京",
    :area => "港区",
    :address1 => "芝大門",
    :address2 => "1-1-1-101",
    :phone => "03-1234-5678",
    :hits => hits,
    :catches => catches,
    :balls => balls,
    :group => group
    )
end
api/groups_controller.rbに追加します
def show
    @group = Group.find(params[:id])
    respond_to do |format|
      format.xml do
        render :xml => @group.to_xml(
          :only => [ :id, :name ],
          :skip_types => true,
          :include => [ :users ]
        )
      end
      format.json { render:json => @group.as_json_with_image }
    end
  end
モデルで表示する user.rbを編集
def to_xml(options = {})
    options[:indent] ||= 2
    xml = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
    xml.user do
      xml.id id
      xml.name name
      xml.sex sex
      xml.country country
      xml.area area
      xml.address1 address1
      xml.address2 address2
      xml.phone phone
      end
    end
  end
モデルを編集 group.rb
def as_json_with_image
    {
      :users => users.map { |u|
        {
          :id => u.id,
          :name => u.name,
          :sex => u.sex,
          :country => u.country,
          :area => u.area,
          :address1 => u.address1,
          :address2 => u.address2,
          :phone => u.phone
        }
      }
    }
  end
ここまでで配列以外を表示することが出来ました。

モデルでAPIを設定する

まずは表示する内容を作成します。
$ rails g controller groups index
      create  app/controllers/groups_controller.rb
       route  get "groups/index"
      invoke  erb
      create    app/views/groups
      create    app/views/groups/index.html.erb
      invoke  test_unit
      create    test/functional/groups_controller_test.rb
      invoke  helper
      create    app/helpers/groups_helper.rb
      invoke    test_unit
      create      test/unit/helpers/groups_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/groups.js.coffee
      invoke    scss
      create      app/assets/stylesheets/groups.css.scss
続いてモデルも
$ rails g model group
      invoke  active_record
      create    db/migrate/20120427055811_create_groups.rb
      create    app/models/group.rb
      invoke    test_unit
      create      test/unit/group_test.rb
      create      test/fixtures/groups.yml
マイグレーションを設定します。
class CreateGroups < ActiveRecord::Migration
  def change
    create_table :groups do |t|
      t.string :name

      t.timestamps
    end
  end
end
おきまりの〜
$ rake db:migrate
seeds/groups.rbを作成
#config: utf-8

group = Group.create!(
  :name => "グループ1"
)
モデルを作成
class Group < ActiveRecord::Base
  validates :name, :presence => true
  validates :name, :uniqueness => true

  attr_accessible :name
end
リセットします。
$ rake db:reset
routes.rbを変更します。
resources :groups, :only => [:index]
api/groups_controller.rbを作成
class Api::GroupsController < ApplicationController
  respond_to :xml, :json

  def index
    @groups = Group.order("created_at DESC")
    respond_to do |format|
      format.json { render:json => @groups.to_json }
      format.xml do
        render :xml => @groups.to_xml(
          :only => [
            :id, :name
          ],
          :skip_types => true
        )
      end
    end
  end
end
Jsonをモデルに書きます。
def as_json(options = {})
    options[:indent] ||= 2
      {
        :id => self.id,
        :name => self.name
      }
  end
これでモデルに分けて見えるようになりました。

API(XmlとJson)の表示〜お知らせをAPIで表示する〜

お知らせを作っていきます。
class CreateAnnouncements < ActiveRecord::Migration
  def change
    create_table :announcements do |t|
      t.string :subject, :null => false
      t.text :content
   
      t.timestamps
    end
  end
end
次にマイグレーションをします。
$ rake db:migrate
次にseeds.rbを設定
table_names = %w(announcements)
table_names.each do |table_name|
  dir = case Rails.env
  when 'development', 'staging'
    'development'
  when 'production'
    'production'
  end
  path = Rails.root.join('db', 'seeds', dir, "#{table_name}.rb")
  if File.exist?(path)
    puts "Creating #{table_name}..."
    require(path)
  end
end
seedsフォルダを作成しその下にdevelopmentフォルダを作成
その下にannouncements.rbを作成
# coding: utf-8
5.times do |n|
  Announcement.create(
    :subject => "第#{n+1}回赤坂リーグに参加しました!!",
    :content => "これは説明です" * 25
  )
end
モデルを変更します。
class Announcement < ActiveRecord::Base
  validates :subject, :content, :presence => true
  validates :subject, :uniqueness => true
  validates :content, :length => { :maximum => 2000 }

attr_accessible :content, :subject
end
attr_accessibleを入れないと
rake aborted!
Can't mass-assign protected attributes: subject, content
となるので付けます。 ルーティングの設定をします。同じ名前で書きたいので「namespace」で分けます。
namespace :api do
    resources :announcements, :only => [ :index ]
end
次にapi/announcements_controller.rbを作成します。
class Api::AnnouncementsController < ApplicationController
  respond_to :xml, :json
 
  def index
    respond_with(
      Announcement.order("created_at DESC"),
      :only => [ :id, :subject, :content ],
      :skip_types => true
    )
  end
end
以上でlocalhost:3000/api/announcements.jsonもしくはxmlで表示されます。

データベースを作成する〜プロジェクトを作る〜

まずは「index」を消します
rm public/index.html
次にトップページを作ります。
$ rails g controller top index
create app/controllers/top_controller.rb
     route get "top/index"
     invoke erb create app/views/top
     create app/views/top/index.html.erb
     invoke test_unit
     create test/functional/top_controller_test.rb
     invoke helper create app/helpers/top_helper.rb
     invoke test_unit
     create test/unit/helpers/top_helper_test.rb
     invoke assets
     invoke coffee
     create app/assets/javascripts/top.js.coffee
     invoke scss create app/assets/stylesheets/top.css.scss
おっとその前にプッシュしちゃいけないのを設定し忘れてました。
$ vi .gitignore
すでにあるのでそれを以下の内容で編集します。

# See http://help.github.com/ignore-files/ for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
#   git config --global core.excludesfile ~/.gitignore_global

# Ignore bundler config
/.bundle

# Ignore the default SQLite database.
db/schema.rb

# Ignore all logfiles and tempfiles.
/log/*.log
/tmp
config/database.yml
.git
.rvmrc
.project
nbproject
次にプロジェクトをnetbeansに追加します。 そしてルーティングの設定です。「routes.rb」を編集します。
Planets::Application.routes.draw do
    root :to => "top#index"
end
次にAPIのHP作るなら作るはずであるお知らせを作ります。
rails g controller announcements index
      create  app/controllers/announcements_controller.rb
       route  get "announcements/index"
      invoke  erb
      create    app/views/announcements
      create    app/views/announcements/index.html.erb
      invoke  test_unit
      create    test/functional/announcements_controller_test.rb
      invoke  helper
      create    app/helpers/announcements_helper.rb
      invoke    test_unit
      create      test/unit/helpers/announcements_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/announcements.js.coffee
      invoke    scss
      create      app/assets/stylesheets/announcements.css.scss
続いてモデルも作成します。
$ rails g model announement
create    db/migrate/20120427050622_create_announcements.rb
      create    app/models/announcement.rb
      invoke    test_unit
      create      test/unit/announcement_test.rb
      create      test/fixtures/announcements.yml
Gemファイルを変更します。以下を追加して「sqlite3」を削除します。
gem 'mysql2'
インストールします。
$ bundle install
database.ymlを変えます。
development:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: planets_development
  pool: 5
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock
 
test:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: planets_test
  pool: 5
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock

production:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: planets_production
  pool: 5
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock
そして最後セットアップです
$ rake db:create
何も出なければ完了です。

Githubで管理する〜プロジェクトを作る〜

まずはプロジェクトを作成しましょう!
$ rails new planets
次にruby1.8.7とruby1.9.3をrvmで分けて使用している場合に以下を設定すると便利
プロジェクト内で
$vi .rvmrc
次の1行を追加
rvm 1.9.3
一度プロジェクトディレクトリーの外へ移動してもう一度入り直すと

==============================================================================
= NOTICE                                                                     =
==============================================================================
= RVM has encountered a new or modified .rvmrc file in the current directory =
= This is a shell script and therefore may contain any shell commands.       =
=                                                                            =
= Examine the contents of this file carefully to be sure the contents are    =
= safe before trusting it! ( Choose v[iew] below to view the contents )      =
==============================================================================
Do you wish to trust this .rvmrc file? (/home/watarusato/seminer/test/planets/.rvmrc)
y[es], n[o], v[iew], c[ancel]>
と出てくるので「y」を選択
次にGithubの自分のアカウントに写り、「New Repository」をクリック
プロジェクト名
別に見えて問題ないのでpublicに設定(プライベートは有料らしい)
そしてRailsに設定して「Create Repository」をクリック
またプロジェクトへ戻ります。
$ git init
Initialized empty Git repository in /home/.../planets/.git/
これでプロジェクトに「.git」が追加されたのであとはプッシュするだけです。
$ git status
$ git add .
$ git commit -m 'first push'
$ git remote add origin git@github.com:アカウント名/プロジェクト名.git
$ git push -u origin master
Counting objects: 63, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (49/49), done.
Writing objects: 100% (63/63), 25.92 KiB, done.
Total 63 (delta 2), reused 0 (delta 0)
To git@github.com:アカウント名/プロジェクト名.git
 * [new branch] master -> master
となり、これで管理までは完了です。

2012年3月13日火曜日

都道府県別に表示する

簡単に都道府県別で分けれたらいいかと思い、プロジェクト内で作成したのでここにメモ

今回重要なのは都道府県をマッチさせなければならない為、登録の際に間違って登録されていたらうまく表示されない。

routes.rb
resources :users

resources :prefectures do
  resources :users
end

usersが存在するという仮定で作成
都道府県表示する為に

users_helper.rb
def choose_region
    %w(北海道 青森県 岩手県 宮城県 秋田県
        山形県 福島県 茨城県 栃木県 群馬県
        埼玉県 千葉県 東京都 神奈川県 新潟県
        富山県 石川県 福井県 山梨県 長野県
        岐阜県 静岡県 愛知県 三重県 滋賀県
        京都府 大阪府 兵庫県 奈良県 和歌山県
        鳥取県 島根県 岡山県 広島県 山口県
        徳島県 香川県 愛媛県 高知県 福岡県
        佐賀県 長崎県 熊本県 大分県 宮崎県
        鹿児島県 沖縄県
      )
  end

users_controller.rb
def index
    if params[:prefecture_id]
      @users = User.where(:PREF => NKF.nkf("-eW", params[:prefecture_id])).order("created_at DESC").paginate(:page => params[:page], :per_page => 40)
    else
      @users = User.where("NAME <> ''").order("created_at DESC").paginate(:page => params[:page], :per_page => 70)
    end
  end


ここでもしパラメーターが[prefecture_id]が来たらwhereで(:PREF)のNKF.nkf("-eW",)で変換されたパラメーター[:prefecture_id]を返すという設定。
NKFは前回でのブログでmysqlの文字コードが違う為に今回仕方なく設定している。

index.html.erb
<h3>都道府県別</h3>
  <% choose_region.collect do |p| %>
    <%= link_to p, prefecture_users_path(:prefecture_id => p),
      :class => 'prefectures' %>
  <% end %>

<div class="organization">
      <% @users.each do |user| %>
        <%= link_to(user.name, user) %><br/>
      <% end %>
    </div>

という形。choose_regionで都道府県をcollectで出し、それぞれをリンクにして、無理矢理prefecture_users_pathで指定しいる
今回は都道府県用のdbを作成しないので無理矢理パスを決めている。

以上のようにすれば都道府県がしっかり登録されていれば都道府県をクリックしたら都道府県別に表示された。

文字コードを変換する方法

今回のプロジェクトは既に昔から動いているperlで出来たHPの管理システム。
mysqlの文字コードはlatin1を使用していてテンパったのでここにメモ

database.ymlを書く

development:
  adapter: mysql2
  encoding: latin1
  database: データベース名
  pool: 5
  username: ユーザー名
  password: パスワード

データベースを作る

  • 今回は既存のデータに追加する形で使う
  • データベースのテーブル名は大文字である
  • 必要なデータだけdevelopmentで再現する
$ rails g model user

class CreateUsers < ActiveRecord::Migration
  def change
    if Rails.env.production?
      add_column :USER, :advertisement, :text
      add_column :USER, :created_at, :datetime
      add_column :USER, :updated_at, :datetime
    else
      create_table :USER do |t|
        t.string :NAME
        t.string :PREF
        t.string :ADDRESS
        t.string :EMAIL
        t.string :TEL
        t.text   :advertisement #今回追加する部分
 
        t.timestamps
      end
    end
  end
end

今回は1つデータベースに追加する形なので小文字がRailsプロジェクトで作成したカラムである。他は開発環境で再現する為に作った開発環境のみのDB。
またRAILS_ENVの時は「add_column」で「USER」テーブルに追加する

次にモデルの編集

class User < ActiveRecord::Base
  self.table_name = "USER" #このままだとseedに挿入出来ないのでテーブル名を指定する。

  attr_accessor :ad, :pref, :address, :name # カラム名を変換

  after_find do # 変換する
    self.pref = self.PREF ? NKF.nkf("-Ew", self.PREF) : ""
    self.ad = self.advertisement ? NKF.nkf("-Ew", self.advertisement) : ""
    self.address = self.ADDRESS ? NKF.nkf("-Ew", self.ADDRESS) : ""
    self.name = self.NAME ? NKF.nkf("-Ew", self.NAME) : ""
  end

  before_save do
    self.PREF = NKF.nkf("-eW", self.pref) if self.pref
    self.ADDRESS = NKF.nkf("-eW", self.address) if self.address
    self.NAME = NKF.nkf("-eW", self.name) if self.name
    self.advertisement = NKF.nkf("-eW", self.ad) if self.ad
  end
end

seedを作成する
中身は以下の通りである。「attr_accessor」で指定したカラム名に変更
テキスト内容は以下の様にして外部ファイルを読み込む

pref = ["北海道","東京都","神奈川県","愛知県","大阪府","福岡県","沖縄県"]
0.upto(200) do |idx|
  User.create!(
    :name => "団体名第 #{idx}",
    :pref => "#{pref[idx % 7]}",
    :address => "#{pref[idx % 7]}",
    :EMAIL => "mail#{idx}@example.com",
    :TEL => "00-1234-5678",
    :ad => File.read(Rails.root.join("db/seeds/advertisement.txt"))
  )
end

あとはcontrollerを設定すれば表示されるはずです。「次は都道府県別に表示する」をメモします。

アプリケーションをサーバに上げる手順

Railsアプリケーションを完成させたのでそれを上げるまでの一連の流れをメモ!

ドメインを作成

レポジトリを作る
$ mkdir hoge
$ hg init
$ ls -a
.  .. .hg
「hg init」でレポジトリが出来るので確認してコミットする
$ hg push ssh://xxx@hg.xxxx.jp/directory/directory/main
.hg/hgrcを編集する
[paths]
default = ssh://xxx@hg.xxxx.jp/directory/directory/main
とすれば以降プッシュが楽になる。

デプロイ先を決める

deploy.rbを書く
require 'bundler/capistrano'
load 'deploy/assets'

set :application, "application名"
set :scm, :mercurial
set :use_sudo, false
set :keep_releases, 3

desc "説明文"
task :production do
  set :deploy_to, "/var/rails/#{application}"
  set :repository, "ssh://xxx@hg.xxxx.jp/directory/directory/main"
  set :user, 'app'
  set :runner, "app"
  set :staging_server, false
  set :use_passenger, true
  set :rails_env, 'production'
  set :resettable, false  role :app, "サーバー"
  role :web, "サーバー"
  role :db,  "サーバー", :primary => true
  set :default_environment, {
    'PATH' => "/usr/local/bin:$PATH",
  }
end

desc "説明文"
after "deploy:update_code", :roles => :app do
  run "cp #{shared_path}/config/database.yml #{release_path}/config/"
end

namespace :deploy do
  # deploy:restart の上書き
  desc "説明文"
  task :restart do
    run "mkdir -p #{shared_path}/tmp"
    run "touch #{shared_path}/tmp/restart.txt"
  end
end
↓ デプロイ先にフォルダを作る
$ cap production deploy:setup
database.ymlを作る
通常のサーバー上であれば
/var/rails/application名/shared/config/database.ymlに書く
production:
  adapter: mysql2
  encoding: 文字コード
  database: application名
  pool: 5
  username: ユーザー名
  password: パスワード
  timeout: 5000

デプロイする
$ cap production deploy

サーバー上で手動でマイグレーションする(deploy.rbに書かない場合)
サーバー上で
$ export RAILS_ENV=production
$ bundle exec rake db:migrate

サーバー上で手動でrake db:assets:precompileをする(deploy.rbに書かない場合)
$ export RAILS_ENV=production
$ bundle exec rake db:assets:precompile

apacheのconfを書く
標準であればこんな感じ?
<VirtualHost *:80>
  ServerName ドメイン
  ErrorLog "/var/log/httpd/ドメイン.error_log"
  CustomLog "/var/log/httpd/ドメイン.access_log"
  DocumentRoot /var/rails/linkage/current/public
  RailsEnv production
  RackEnv production
  PassengerRestartDir /var/rails/linkage/shared/tmp
</VirtualHost>

apacheのconfが間違っていないか確認
$ sudo ./apachectl configtest
apacheの再起動
$ sudo ./apachectl graceful

2012年2月9日木曜日

Centos6.2にRailsプロジェクトを入れる

VMwarePlayerにCentOs6.2「basic server」をインストールしてプロジェクトを載せるまで実行してみました。 環境はUbuntu11.10デスクトップ版64bitです。VMwareはバージョン4です。 まずインストーラーが動き始めたら以下のように進めます。






ここで注意!!ネットワークの設定をしなければあとで面倒なのでここで設定します。
「Configure Network」をクリックし、編集から「自動接続」にチェックを入れます。









ここまでが一連のインストールの流れです。
あとはhttp://www.oiax.jp/books/kiso_rails2/prepare_centos_6_2.html
に沿って入れるのですが、Rails3.2で

$ rake assets:precompile

とするとエラーが「Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs for a list of available runtimes.」と出たため 調べた結果Gemfileに以下を追加

gem 'execjs'
gem 'therubyracer'

$ bundle install

とすれば通ります。参考:http://stackoverflow.com/questions/6282307/rails-3-1-execjs-and-could-not-find-a-javascript-runtime
またCentOsの場合は問題ないですが、Ubuntuでsudo権限を与える為には

$ sudo adduser hoge admin

でsudoが使える様になります。 また参考ページにはないので参考までに載せると、VMwarePlayer上のサーバーにブラウザからアクセスするには
/etc/hostsに以下のように加えます。

192.168.251.130 hoge.jp

これでブラウザにhoge.jpとすれば繋がります。 ちなみにsshで繋げるには

$ ssh ubuntu@192.168.251.130

となりますユーザー名にIPアドレスです。 これで一通りテスト環境は整いました。
参考ページ


CentOsのインストール:http://www.kkaneko.com/rinkou/linux/centosinstall.html
Railsのプロジェクトを本番環境に載せる:http://www.oiax.jp/books/kiso_rails2.html
rake assets:precompileのエラーについて:http://stackoverflow.com/questions/6282307/rails-3-1-execjs-and-could-not-find-a-javascript-runtime

2012年2月6日月曜日

Jquery-UIのdraggableを使ってみた

これから仕事で使いそうなプラグインを使ってみた。

Ruby on Rails 3.2
Ruby 1.9.3
ほぼ見た目はbloggerのマネです。
Jquery UIを使用するときはapplication.jsに
//= require jquery-ui
を追加しあとは流れで
まずtop/index.html.erbに

<h1>Jqueryを遊ぶ</h1>
<table width="50%">
<tr>
 <td align="left" style="vertical-align: top;">
      <div class="a">ブログ全体</div>
    </td>
  </tr>
  <tr>
    <td align="left" style="vertical-align: top;">
      <table width="100%">
        <tr>
          <td align="left" style="vertical-align: top;">
            <div class="b">
              <div class="c" id="">
                <input type="text" tabindex="-1" style="opacity:0; height: 1px; width: 1px; z-index: -1; overflow-x: hidden; overflow-y: hidden; position: absolute;">
                <div class="d">
                  <div class="e"></div>
                  <span class="f"></span>
                  <div class="g">最大</div>
                  <div class="h">最小</div>
                </div>
              </div>
            </div>
          </td>
        </tr>
      </table>
    </td>
  </tr>
</table>

としてassets/stylesheets/top.css.scss

body {
  background-color:#ccc;
}
.a{
  color:#fff;
  font-size:13px;
  font-weight:bold;
  margin-left:5px;
}
.b{
  width:500px;
}
.c{
  height:20px;
  margin: 0 6px 10px;
}
.d{
  position:relative;
}
.e{
  width: 488px;
  background-color: #999;
  barder: none;
  height:9px;
  overflow:hidden;
  position: absolute;
  top:6px;
}
span.f{
  cursor: pointer;
  position: absolute;
  top: 0;
  z-index: 1px;
  height:21px;
  width:11px;
  background:#fff;
}
.g {
  position:absolute;
  font-size:85%;
  top:24px;
}
.h{
  position: absolute;
  font-size:85%;
  top:24px;
  right:0;
}

最後にassets/javascripts/change.jsを作成し

jQuery (function() {
  jQuery('span.f').draggable({
    containment: 'div.d' ,
    scroll: false,
     axis: "x"
  });
});

遊んでみました。

Windowsでthinを使う

Windowsでthinを使う

今回セミナーでthinを使用したところ、Windows使用の方々が全員エラーになったので
ここにメモをしておく。
環境は「Rails3.2.1」、「Ruby 1.9.3」

Gemfileを以下のように修正


gem 'thin'
gem 'eventmachine', '1.0.0.beta.4.1'

とし、

$ bundle install

=> Booting Thin
=> Rails 3.2.1 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
>> Thin web server (v1.3.1 codename Triple Espresso)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:3000, CTRL+C to stop

できました!!

2012年2月2日木曜日

Virtualbox内のUbuntu-serverにローカルのUbuntu11.10からsshでアクセス

今回はUbuntu11.10上のVirtualbox内のUbuntu-serverにUbuntu11.10からsshでアクセスしてみました。

まずは「設定」→「ネットワーク」→「アダプタ1」→「割り当て」→「NAT」にして「高度」を展開します。
「ポートフォワーディング」をクリックします


次にプロトコルTCP、「ホストポート」を「2222」にし「ゲストポート」を「22」と設定します。


あとは公開鍵を設定するのですが、なかなかこのままでは難しいので簡単な方法として・・・

DropBoxを使用すると簡単です!!

公開鍵を「public」内に保存します。dropbox内でそのファイルを右クリックし「dropbox」から「パブリックリンクのコピー」を選択しテキストエディターか何かに貼り付けます。

するとhttp://dl.dropbox.com/u/xxxxxxxx/id_dsa.pub
という形のURLになります。

そしてゲストubuntu内で

$ wget http://dl.dropbox.com/u/xxxxxxxxx/id_dsa.pub

とし、あとは

$ mv id_dsa.pub authorized_keys

とすればOKです

んで、ホストubuntu内で

$ sudo ssh -p 2222 -l wataru localhost

これで繋がるはずです!!