Railsガイド「Active Record の関連付け (アソシエーション)」の章にある、 4.6 関連付けの拡張
のCustomer
クラスを写経しました。
モデル作成・マイグレーションの実行
$> rails g model customer name
$> rails g model order name region_id customer:references
$> rails db:migrate
上記により、以下の2モデルが作成されました。
class Customer < ApplicationRecord
end
class Order < ApplicationRecord
belongs_to :customer
end
MySQLのSHOW CREATE TABLE
でテーブルを確認します。
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;
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
を以下
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
を付加して以下とする。
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>
と、ここまでは基本の範囲内。
ここからが本題で、Customer
の ordes
にRailsガイドに記載のようにブロックを付加して、以下とする。
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
)のクラス何ですの?と思ったので、
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_id
が202
のOrder
の配列となっていた。
最後に、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
オブジェクトへの参照となっており、
色々、あれやこれやの実行時情報が入っている。
以上です。
何か確認のやり方がおかしい点などあれば、ご指摘ください。