LoginSignup
3
2

More than 5 years have passed since last update.

関連付けの拡張(Railsガイド写経)

Posted at

  Railsガイド「Active Record の関連付け (アソシエーション)」の章にある、 4.6 関連付けの拡張
Customerクラスを写経しました。

モデル作成・マイグレーションの実行

$> rails g model customer name
$> rails g model order name region_id customer:references
$> rails db:migrate

上記により、以下の2モデルが作成されました。

models/customer.rb
class Customer < ApplicationRecord
end
models/order.rb
class Order < ApplicationRecord
  belongs_to :customer
end

MySQLのSHOW CREATE TABLE でテーブルを確認します。

customersテーブル
CREATE TABLE `customers` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ordersテーブル
CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `region_id` varchar(255) DEFAULT NULL,
  `customer_id` int(11) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_orders_on_customer_id` (`customer_id`),
  CONSTRAINT `fk_rails_3dad120da9` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

テストデータの作成

以下の要領で作成

  • customersとordersとで、idの開始値を分かりやすく、なるべく離れた値になるように変えておく。
  • Railsガイドのコードregion_id: order_number[0..2]にそって、region_id は3文字の文字列で、数個作成
  • orders のnameカラムには長さ16のランダムな文字列を作成( ランダムな文字列生成を使わせて頂きました。)
  • 一つのCustomerにつき、20個以上30個以下のOrderを紐づける。

seeds.rb

上記にそって、seeds.rbを以下

db/seeds.rb
ActiveRecord::Base.connection_pool.with_connection { |con|
  con.execute('ALTER TABLE customers AUTO_INCREMENT=201')
  con.execute('ALTER TABLE orders AUTO_INCREMENT=9001')
}

region_ids = %w(A01 F06 K55 P24 S99 X40)

customer_names = (1..3).map {|n| "顧客-#{n}"}

customer_names.each do |n|

  c = Customer.create(name: n)

  num_orders = (20..30).to_a.sample
  num_orders.times do
    order_name = Array.new(16){[*:a..:z,*0..9].sample}.join
    Order.create(name: order_name, customer: c, region_id: region_ids.sample)
  end

end

のように作成してからの、

$> rails db:seed

にてサンプルデータ生成

確認

Customerに、まずは単に has_many :ordersを付加して以下とする。

models/customer.rb
class Customer < ApplicationRecord
  has_many :orders
end

rails console で確認

[ykt68@macbook testapp]$ rails c
Running via Spring preloader in process 15958
Loading development environment (Rails 5.0.1)
irb(main):001:0> customer = Customer.find 202
  Customer Load (0.5ms)  SELECT  `customers`.* FROM `customers` WHERE `customers`.`id` = 202 LIMIT 1
=> #<Customer id: 202, name: "顧客-2", created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">
irb(main):002:0> customer.orders
  Order Load (0.6ms)  SELECT `orders`.* FROM `orders` WHERE `orders`.`customer_id` = 202
=> #<ActiveRecord::Associations::CollectionProxy [#<Order id: 9031, name: "illm3h622nq1744u", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9032, name: "s76ak83jlys0oww4", region_id: "X40", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9033, name: "ep2jkr5htseo9l4m", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9034, name: "t8t9s6xs3jwtnwjs", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9035, name: "wx72xtzslx6cbtin", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9036, name: "7qqdapbggj35dffv", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9037, name: "m7vyztkdxv6hgbkr", region_id: "F06", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9038, name: "0enruf5cfxj930tn", region_id: "S99", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9039, name: "zg9zdgpjom8lpitn", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9040, name: "ujwk6eklncw4idru", region_id: "S99", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, ...]>
irb(main):003:0>

と、ここまでは基本の範囲内。

ここからが本題で、Customerordes にRailsガイドに記載のようにブロックを付加して、以下とする。

models/customer.rb
class Customer < ApplicationRecord
  has_many :orders do
    def find_by_order_prefix(order_number)
      find_by(region_id: order_number[0..2])
    end
  end
end

  上記の状態で、rails console で、find_by_order_prefixを使ってみる。
find_by_order_prefixメソッドの引数order_numberの最初の3文字が
region_idであるという想定のコードなので、それっぽいorder_number
値を渡してやる。

[ykt68@macbook testapp]$ rails c
Running via Spring preloader in process 16540
Loading development environment (Rails 5.0.1)
irb(main):001:0> customer = Customer.find 202
  Customer Load (0.5ms)  SELECT  `customers`.* FROM `customers` WHERE `customers`.`id` = 202 LIMIT 1
=> #<Customer id: 202, name: "顧客-2", created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">
irb(main):002:0> customer.orders.find_by_order_prefix('K55-1234-9999')
  Order Load (0.5ms)  SELECT  `orders`.* FROM `orders` WHERE `orders`.`customer_id` = 202 AND `orders`.`region_id` = 'K55' LIMIT 1
=> #<Order id: 9031, name: "illm3h622nq1744u", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">
irb(main):003:0>

It worked !

で、find_by_order_prefixメソッドの中で、self(つまり、orders)のクラス何ですの?と思ったので、

models/customer.rb
    def find_by_order_prefix(order_number)
      p self
      find_by(region_id: order_number[0..2])
    end

として、再度rails consoleで確認してみると、こんなん出ました。

[ykt68@macbook testapp]$ rails c
Running via Spring preloader in process 17124
Loading development environment (Rails 5.0.1)
irb(main):001:0> customer = Customer.find 202
  Customer Load (0.5ms)  SELECT  `customers`.* FROM `customers` WHERE `customers`.`id` = 202 LIMIT 1
=> #<Customer id: 202, name: "顧客-2", created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">
irb(main):002:0> customer.orders.find_by_order_prefix('K55-1234-9999')
  Order Load (0.6ms)  SELECT `orders`.* FROM `orders` WHERE `orders`.`customer_id` = 202
#<ActiveRecord::Associations::CollectionProxy [#<Order id: 9031, name: "illm3h622nq1744u", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9032, name: "s76ak83jlys0oww4", region_id: "X40", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9033, name: "ep2jkr5htseo9l4m", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9034, name: "t8t9s6xs3jwtnwjs", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9035, name: "wx72xtzslx6cbtin", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9036, name: "7qqdapbggj35dffv", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9037, name: "m7vyztkdxv6hgbkr", region_id: "F06", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9038, name: "0enruf5cfxj930tn", region_id: "S99", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9039, name: "zg9zdgpjom8lpitn", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9040, name: "ujwk6eklncw4idru", region_id: "S99", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, ...]>
  Order Load (0.7ms)  SELECT  `orders`.* FROM `orders` WHERE `orders`.`customer_id` = 202 AND `orders`.`region_id` = 'K55' LIMIT 1
=> #<Order id: 9031, name: "illm3h622nq1744u", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">
irb(main):003:0>

なので、selfは、ActiveRecord::Associations::CollectionProxyオブジェクトでした。

さらに、Railsガイドに

関連付けプロキシの内部を参照するには、proxy_association アクセサにある以下の 3 つの属性を使用します。

・proxy_association.owner は、関連付けを所有するオブジェクトを返します。 

・proxy_association.reflection は、関連付けを記述するリフレクションオブジェクトを返します。

・proxy_association.target は、belongs_to または has_one 関連付けのオブジェクトを返すか、has_many または has_and_belongs_to_many 関連付けオブ ジェクトのコレクションを返します。

との記載があるので、以下、これらを確認。

[ykt68@macbook testapp]$ rails c
Running via Spring preloader in process 18584
Loading development environment (Rails 5.0.1)
irb(main):001:0> customer = Customer.find 202
  Customer Load (0.6ms)  SELECT  `customers`.* FROM `customers` WHERE `customers`.`id` = 202 LIMIT 1
=> #<Customer id: 202, name: "顧客-2", created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">

proxy_association.owner は、

irb(main):002:0> customer.orders.proxy_association.owner
=> #<Customer id: 202, name: "顧客-2", created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">

Customerオブジェクトへの参照が入っている。
proxy_association.tager

irb(main):003:0> customer.orders.proxy_association.target
=> []

現時点では空の配列。
今回追加した、find_by_order_prefix を呼んでみる。

irb(main):004:0> customer.orders.find_by_order_prefix('K55-1234-9999')
  Order Load (0.9ms)  SELECT `orders`.* FROM `orders` WHERE `orders`.`customer_id` = 202
#<ActiveRecord::Associations::CollectionProxy [#<Order id: 9031, name: "illm3h622nq1744u", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9032, name: "s76ak83jlys0oww4", region_id: "X40", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9033, name: "ep2jkr5htseo9l4m", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9034, name: "t8t9s6xs3jwtnwjs", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9035, name: "wx72xtzslx6cbtin", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9036, name: "7qqdapbggj35dffv", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9037, name: "m7vyztkdxv6hgbkr", region_id: "F06", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9038, name: "0enruf5cfxj930tn", region_id: "S99", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9039, name: "zg9zdgpjom8lpitn", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9040, name: "ujwk6eklncw4idru", region_id: "S99", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, ...]>
  Order Load (0.5ms)  SELECT  `orders`.* FROM `orders` WHERE `orders`.`customer_id` = 202 AND `orders`.`region_id` = 'K55' LIMIT 1
=> #<Order id: 9031, name: "illm3h622nq1744u", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">

そして、proxy_association.targetを見ると、

irb(main):005:0> customer.orders.proxy_association.target
=> [#<Order id: 9031, name: "illm3h622nq1744u", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9032, name: "s76ak83jlys0oww4", region_id: "X40", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9033, name: "ep2jkr5htseo9l4m", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9034, name: "t8t9s6xs3jwtnwjs", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9035, name: "wx72xtzslx6cbtin", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9036, name: "7qqdapbggj35dffv", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9037, name: "m7vyztkdxv6hgbkr", region_id: "F06", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9038, name: "0enruf5cfxj930tn", region_id: "S99", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9039, name: "zg9zdgpjom8lpitn", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9040, name: "ujwk6eklncw4idru", region_id: "S99", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9041, name: "n8muqxe3xob3gxw0", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9042, name: "e98oq2wchhq8az7o", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9043, name: "7mfqve9aflehtb4e", region_id: "F06", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9044, name: "9vcsti7fg2sdsspa", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9045, name: "ygd6iu22qkhhgq3s", region_id: "P24", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9046, name: "h9wbvnizy2gc1m0w", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9047, name: "nso9r1xf03kl0pq4", region_id: "F06", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9048, name: "wuz20tg3fodypio2", region_id: "X40", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9049, name: "95is5jiwnpfttpfw", region_id: "F06", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9050, name: "4q4h20379rjgneac", region_id: "S99", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9051, name: "89tojln9j1vyraqd", region_id: "P24", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9052, name: "ge19q22g3xfjvlpq", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9053, name: "2x3c9tg62rsgsjze", region_id: "X40", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9054, name: "f8b78zxvfjeks2f7", region_id: "X40", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9055, name: "qmdncvg63tx1hkfx", region_id: "S99", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9056, name: "tfgtgan6w4q2vxtx", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9057, name: "0h7tvjuy31mk8mul", region_id: "F06", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9058, name: "v1nbec4njebsm08x", region_id: "K55", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9059, name: "e3uyudy3jiust1gp", region_id: "X40", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">, #<Order id: 9060, name: "cun0uhbwj4a9m0za", region_id: "A01", customer_id: 202, created_at: "2017-02-21 05:17:17", updated_at: "2017-02-21 05:17:17">]
irb(main):006:0> 

と、region_idが、K55ではないOrderも含めた
customer_id202Orderの配列となっていた。

最後に、proxy_association.reflectionは、

irb(main):006:0> customer.orders.proxy_association.reflection
=> #<ActiveRecord::Reflection::HasManyReflection:0x007fb3f4b6a0d0 @name=:orders, @scope=#<Proc:0x007fb3f4b6a0f8@/Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activerecord-5.0.1/lib/active_record/associations/builder/collection_association.rb:79>, @options={}, @active_record=Customer(id: integer, name: string, created_at: datetime, updated_at: datetime), @klass=Order(id: integer, name: string, region_id: string, customer_id: integer, created_at: datetime, updated_at: datetime), @plural_name="orders", @automatic_inverse_of=false, @type=nil, @foreign_type="orders_type", @constructable=true, @association_scope_cache={}, @scope_lock=#<Mutex:0x007fb3f4b69f18>, @class_name="Order", @foreign_key="customer_id", @active_record_primary_key="id">
irb(main):007:0>

と、ActiveRecord::Reflection::HasManyReflection オブジェクトへの参照となっており、
色々、あれやこれやの実行時情報が入っている。

以上です。
何か確認のやり方がおかしい点などあれば、ご指摘ください。

参考

ActiveRecord::Associations::CollectionProxy

3
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2