「RailsによるアジャイルWebアプリケーション開発」18章読み中
Active Recordその2
今度はテーブル間のリレーションシップに関する話題です。
長いので一旦書く。
1対1のリレーションシップ
ここでいきなり、これまでに出てきていないInvoiceモデルが登場します。
この程度はわざわざちゃんと説明しなくても理解しろよってことか?
ここでは真面目にやってみる。
とりあえずInvoiceモデルを作ってみる。
>ruby script/generate model invoice ruby script/generate model invoice exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/invoice.rb create test/unit/invoice_test.rb create test/fixtures/invoices.yml exists db/migrate create db/migrate/010_create_invoices.rb >
で、db/migrate/010_create_invoices.rbを編集
class CreateInvoices < ActiveRecord::Migration def self.up create_table :invoices do |t| t.column :order_id, :integer t.column :name, :string t.timestamps end end def self.down drop_table :invoices end end
2カラム追加しました。
order_idは、Orderモデルへの外部キー、nameは請求先の名前です。
で、早速マイグレート。
>rake db:migrate rake db:migrate (in c:/InsRails/rails_apps/depot) == 9 Irreversible: migrating ================================================== == 9 Irreversible: migrated (0.0000s) ========================================= == 10 CreateInvoices: migrating =============================================== -- create_table(:invoices) -> 0.3120s == 10 CreateInvoices: migrated (0.3120s) ====================================== >
009のマイグレートは前回仕込んだIrreversibleのワナがあったので、それを除去した上でやりました。無事行ったようです。
で、ここからが本題。
まず、Orderモデルにhas_oneを追加します。
class Order < ActiveRecord::Base has_one :invoice end
次にInvoiceモデルにbelongs_toを追加します。
class Invoice < ActiveRecord::Base belongs_to :order end
これでつながったはず。
ちゃんと動くかどうかテストコードを書いてみる。
require 'rubygems' require 'activerecord' ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "../db/development.sqlite3") class Order < ActiveRecord::Base has_one :invoice end class Invoice < ActiveRecord::Base belongs_to :order end order1 = Order.create( :name => "taka_2", :address => "Kanagawa prefecture", :email => "taka_2@test.com", :payment_type_id => 1) Invoice.create( :order_id => order1.id, :name => "taka_22") p order1 p order1.invoice # 後始末 Invoice.delete(order1.invoice.id) Order.delete(order1.id)
結果はこう。
#<Order id: 22, name: "taka_2", address: "Kanagawa prefecture", email: "taka_2@test.com", created_at: "2008-06-15 10:38:39", updated_at: "2008-06-15 10:38:39", payment_type_id: 1> #<Invoice id: 2, order_id: 22, name: "taka_22", created_at: "2008-06-15 10:38:39", updated_at: "2008-06-15 10:38:39">
無事できました。
createメソッドには、idは指定できないのですね。。
こんなエラーが出てハマりました。
C:/InsRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:2313:in `remove_attributes_protected_from_mass_assignment': undefined method `debug' for nil:NilClass (NoMethodError) from C:/InsRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:2114:in `attributes=' from C:/InsRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:1926:in `initialize' from C:/InsRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:571:in `new' from C:/InsRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:571:in `create' from ar5.rb:14
nil:NilClassにdebugメソッドが無いというエラーのようですが、これでバグ探せって言われても無理があります。。
1対多のリレーションシップ
これは10章でやっているので飛ばし。
多対多のリレーションシップ
ここでもまた新しいモデルが登場します。
ので、早速作ってみる。
>ruby script/generate model category ruby script/generate model category exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/category.rb create test/unit/category_test.rb create test/fixtures/categories.yml exists db/migrate create db/migrate/011_create_categories.rb >ruby script/generate migration categories_products ruby script/generate migration categories_products exists db/migrate create db/migrate/012_categories_products.rb >
まずは、011_create_categories.rbの編集。
class CreateCategories < ActiveRecord::Migration def self.up create_table :categories do |t| t.column :name, :string t.timestamps end end def self.down drop_table :categories end end
name列を追加しました。
次に、012_categories_products.rbの編集。
class CategoriesProducts < ActiveRecord::Migration def self.up create_table :categories_products, :id => false do |t| t.column :category_id, :integer, :null => false t.column :product_id, :integer, :null => false end end def self.down drop_table :categories_products end end
結合テーブルなので、id列を作らないようにしているのがミソですな。
でもってマイグレート
>rake db:migrate rake db:migrate (in c:/InsRails/rails_apps/depot) == 11 CreateCategories: migrating ============================================= -- create_table(:categories) -> 0.1400s == 11 CreateCategories: migrated (0.1400s) ==================================== == 12 CategoriesProducts: migrating =========================================== -- create_table(:categories_products, {:id=>false}) -> 0.1870s == 12 CategoriesProducts: migrated (0.1870s) ================================== >
試しにsqlite3コマンドでcategories_productsテーブルのレイアウトを見てみる。
.schema categories_products CREATE TABLE categories_products ("category_id" integer NOT NULL, "product_id" integer NOT NULL);
ちゃんとid列無しで作られていますね。
で、ここからが本題。
まず、Categoryモデルにhas_and_belongs_to_manyを追加します。
class Category < ActiveRecord::Base has_and_belongs_to_many :products end
次にProductモデルに同じくhas_and_belongss_to_manyを追加します。
class Product < ActiveRecord::Base has_and_belongs_to_many: categories end
これでつながったはず。
ちゃんと動くかどうかテストコードを書いてみる。
require 'rubygems' require 'activerecord' ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "../db/development.sqlite3") class Category < ActiveRecord::Base has_and_belongs_to_many :products end class Product < ActiveRecord::Base has_and_belongs_to_many :categories end p1 = Product.create( :title => "Google Hacks", :description => "Google Hacks second edition", :image_url => "gh.png", :price => 3200 ) c1 = Category.create( :name => "Web Technique" ) c2 = Category.create( :name => "O'reilly" ) p1.categories << c1 p1.categories << c2 p1.categories.each { |c| puts(c.name) puts(c.products[0].title) } # 後始末 # 関連テーブルも行削除するためには、この行が必要。 p1.categories.clear() Product.delete(p1) Category.delete(c1) Category.delete(c2)
結果はこう。
Web Technique Google Hacks O'reilly Google Hacks
というわけで、無事相互参照できてそう。
最初関連テーブルのレコードが削除されなかったのですが、
配列をクリアしてからdeleteすることで削除されました。