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