RailsによるアジャイルWebアプリケーション開発

第19章 続き

表示についてはhirbを使ってみた。
.pryrcに設定が必要。

FAQ · pry/pry Wiki · GitHub

CRUD

  • create は attributes の配列渡すことで複数件同じ登録が可能
  • find_by_attribute_name って廃止されたかと思っていたが、されていなかった(DEPRECATEDも出ない)
  • find_or_initialize_by は attributes_name を続けて書くことは出来ないので、引数で指定
    • whereメソッドの中で直接SQL文を書けるが、whereメソッドを分けてorを使うことはできない
    • Arelを使ってやればRubyっぽく記述できるよううだが、分かり難く、見辛いように見えるので、where内SQL記述を推したい
[5] pry(main)> User.where("(name like :name1 and name like :name2) or name = :name3", name1: "%A%", name2: "%S%", name3: "Mable Kutch")
  User Load (1.0ms)  SELECT "users".* FROM "users" WHERE ((name like '%A%' and name like '%S%') or name = 'Mable Kutch')
+----+----------------------+-----------------------------+-------------------------+-------------------------+
| id | name                 | email                       | created_at              | updated_at              |
+----+----------------------+-----------------------------+-------------------------+-------------------------+
| 21 | Mrs. Tatum Gislason  | laura@example.net           | 2015-01-30 10:26:09 UTC | 2015-01-30 10:26:09 UTC |
| 24 | Vincenzo Jacobs      | antonina@example.net        | 2015-01-30 10:26:10 UTC | 2015-01-30 10:26:10 UTC |
| 25 | Mable Kutch          | darlene@example.org         | 2015-01-30 10:26:10 UTC | 2015-01-30 10:26:10 UTC |
| 26 | Ms. Sonia Wehner     | jillian.goodwin@example.com | 2015-01-30 10:26:10 UTC | 2015-01-30 10:26:10 UTC |
| 27 | Sarah Bailey         | stacy_haag@example.com      | 2015-01-30 10:26:10 UTC | 2015-01-30 10:26:10 UTC |
| 30 | Miss Agustin Kuvalis | alana_cassin@example.com    | 2015-01-30 10:26:10 UTC | 2015-01-30 10:26:10 UTC |
+----+----------------------+-----------------------------+-------------------------+-------------------------+
6 rows in set
  • joinsメソッド(INNER JOIN)を使用すると、saveが使えない(readonly)そうだけど、普通に更新できちゃう
[6] pry(main)> u = User.joins(:books).first
  User Load (0.6ms)  SELECT  "users".* FROM "users" INNER JOIN "book_shelves" ON "book_shelves"."user_id" = "users"."id" INNER JOIN "books" ON "books"."id" = "book_shelves"."book_id"  ORDER BY "users"."id" ASC LIMIT 1
+----+---------------------+-------------------+-------------------------+-------------------------+
| id | name                | email             | created_at              | updated_at              |
+----+---------------------+-------------------+-------------------------+-------------------------+
| 21 | Mrs. Tatum Gislason | laura@example.net | 2015-01-30 10:26:09 UTC | 2015-01-30 10:26:09 UTC |
+----+---------------------+-------------------+-------------------------+-------------------------+
1 row in set
[7] pry(main)> u.name = Faker::Name.name
=> "Kellie Muller II"
[8] pry(main)> u.save
   (0.3ms)  begin transaction
  SQL (0.7ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Kellie Muller II"], ["updated_at", "2015-01-30 10:43:15.210122"], ["id", 21]]
   (119.8ms)  commit transaction
=> true
[9] pry(main)> u.reload
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 21]]
+----+------------------+-------------------+-------------------------+-------------------------+
| id | name             | email             | created_at              | updated_at              |
+----+------------------+-------------------+-------------------------+-------------------------+
| 21 | Kellie Muller II | laura@example.net | 2015-01-30 10:26:09 UTC | 2015-01-30 10:43:15 UTC |
+----+------------------+-------------------+-------------------------+-------------------------+
1 row in set
  • lockについては各種DBについて意識する必要はありそう
  • grouping

depotで使用したOrderにカラムを追加するような形なので、別プロジェクト作ってモデル作製し試した。
ffakerだとFaker::Address.state_abbrが使えなかったのでfakerに。

  • 検索+集約関数
[9] pry(main)> Order.where("amount > 20").minimum(:amount)
   (0.3ms)  SELECT MIN("orders"."amount") FROM "orders" WHERE (amount > 20)
=> 22
  • 集約関数とorder, limit
[11] pry(main)> result = Order.group(:state).order("max(amount) desc").limit(3)
  Order Load (0.5ms)  SELECT  "orders".* FROM "orders" GROUP BY state  ORDER BY max(amount) desc LIMIT 3
+----+-------------------+------------------+----------------------------+----------+-------+--------+-------------------------+-------------------------+
| id | name              | address          | email                      | pay_type | state | amount | created_at              | updated_at              |
+----+-------------------+------------------+----------------------------+----------+-------+--------+-------------------------+-------------------------+
| 20 | Virginie Bernier  | North Clinthaven | amos.bechtelar@example.org | 現金     | KS    | 93     | 2015-01-30 10:52:22 UTC | 2015-01-30 10:52:22 UTC |
| 17 | Vallie Hamill III | East Brycen      | evans@example.org          | 現金     | PA    | 49     | 2015-01-30 10:52:22 UTC | 2015-01-30 10:52:22 UTC |
| 19 | Mohammad Hickle   | New Vaughnburgh  | bailey_morar@example.org   | カード   | WV    | 93     | 2015-01-30 10:52:22 UTC | 2015-01-30 10:52:22 UTC |
+----+-------------------+------------------+----------------------------+----------+-------+--------+-------------------------+-------------------------+
  • scope
class Order < ActiveRecord::Base
  PAY_TYPES = %w(現金 カード 注文書)

  scope :last_n_days, ->(days) { where('updated_at < ?', days) }
  scope :checks, -> { where(pay_type: "現金") }
end
[3] pry(main)> Order.last_n_days(Time.now)
  Order Load (0.3ms)  SELECT "orders".* FROM "orders" WHERE (updated_at < '2015-01-30 11:04:43.263452')

[4] pry(main)> Order.checks
  Order Load (0.2ms)  SELECT "orders".* FROM "orders" WHERE "orders"."pay_type" = ?  [["pay_type", "現金"]]
  • find_by_sql で独自SQL
  • SQL内の属性名を認識できるので便利
  • 「罪の意識を感じる必要はない」
  • 外出しSQLにしたいな
  • delete と destory
  • delete はコールバックと検証機能を迂回
  • コールバック
  • after_findの空メソッドは作る必要無し
  • config/initializers/encrypter.rb
class ActiveRecord::Base
  def self.encrypt(*attr_names)
    encrypter = Encrypter.new(attr_names)

    before_save encrypter
    after_save encrypter
    after_find encrypter
  end
end
  • app/models/order.rb
class Order < ActiveRecord::Base
  PAY_TYPES = %w(現金 カード 注文書)

  before_validation CreditCardCallbacks.new

  after_create do |order|
    logger.info "after_create 注文 #{order.id} が作製されました"
  end

  scope :last_n_days, ->(days) { where('updated_at < ?', days) }
  scope :checks, -> { where(pay_type: "現金") }

  encrypt(:name, :email)
end
  • 実行
[3] pry(main)> Order.all
  Order Load (0.2ms)  SELECT "orders".* FROM "orders"
+----+------------------+------------------+------------------------------+----------+-------+--------+-------------------------+-------------------------+------------------+
| id | name             | address          | email                        | pay_type | state | amount | created_at              | updated_at              | cc_number        |
+----+------------------+------------------+------------------------------+----------+-------+--------+-------------------------+-------------------------+------------------+
| 1  | Merlin Langosh V | East Elouiseport | hazel.hane@example.net       | 注文書   | IL    | 87     | 2015-01-30 12:05:41 UTC | 2015-01-30 12:05:41 UTC | 1212122111211234 |
| 2  | Jody Hamill      | New Hayliefort   | vivien_heathcote@example.org | 現金     | WV    | 39     | 2015-01-30 12:05:41 UTC | 2015-01-30 12:05:41 UTC | 1234212112211211 |
| 3  | Marguerite Beier | North Ruthe      | elinor.wehner@example.org    | カード   | MD    | 66     | 2015-01-30 12:05:41 UTC | 2015-01-30 12:05:41 UTC | 1211122112342201 |
| 4  | Bette Steuber    | East Helentown   | dale_kautzer@example.org     | 現金     | FL    | 6      | 2015-01-30 12:05:41 UTC | 2015-01-30 12:05:41 UTC | 1234212112211211 |
| 5  | Otto Kling       | Lillianborough   | patience.okuneva@example.com | カード   | RI    | 81     | 2015-01-30 12:05:41 UTC | 2015-01-30 12:05:41 UTC | 1212122111211234 |
+----+------------------+------------------+------------------------------+----------+-------+--------+-------------------------+-------------------------+------------------+
5 rows in set
% sqlite3 -line db/development.sqlite3 "select name, email from orders"
name = Mfsmjo Lbohpti V
email = ibafm.ibof@fybnqmf.ofu

name = Jpez Hbnjmm
email = wjwjfo_ifbuidpuf@fybnqmf.psh

name = Mbshvfsjuf Bfjfs
email = fmjops.xfiofs@fybnqmf.psh

name = Bfuuf Sufvcfs
email = ebmf_lbvuafs@fybnqmf.psh

name = Ouup Kmjoh
email = qbujfodf.plvofwb@fybnqmf.dpn
  • オブザーバ

Rails4では別Gemに。

rails/rails-observers · GitHub

Observerというより、AOPを実現するための方法か。
と思ったらそのような記述があった。

このようにしてオブザーバは、Java のような言語における第一世代のアスペクト指向プログラミングの利点を Rails にもたらしてくれます

  • app/models/order_observer.rb
class OrderObserver < ActiveRecord::Observer
  def after_save(an_order)
    an_order.logger.info("observer 注文 #{an_order.id} が作製されました")
  end
end
  • config/applications.rb
    config.active_record.observers = :order_observer
  • 実行
[4] pry(main)> FactoryGirl.create(:order)
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.2ms)  INSERT INTO "orders" ("name", "email", "cc_number", "address", "pay_type", "state", "amount", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)  [["name", "Hbwfo Sdibfgfs"], ["email", "nbswjo.iboe@fybnqmf.dpn"], ["cc_number", "1212122111211234"], ["address", "Port Anika"], ["pay_type", "注文書"], ["state", "NV"], ["amount", 9], ["created_at", "2015-01-30 13:04:20.482480"], ["updated_at", "2015-01-30 13:04:20.482480"]]
after_create 注文 9 が作製されました
observer 注文 9 が作製されました

ロールバックしたからといって、オブジェクトの値は変化しないので注意。