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
となり、これで管理までは完了です。