内容へ移動
Cat Paw Software
ユーザ用ツール
ログイン
サイト用ツール
検索
ツール
文書の表示
以前のリビジョン
バックリンク
最近の変更
メディアマネージャー
サイトマップ
ログイン
>
最近の変更
メディアマネージャー
サイトマップ
トレース:
rails:active_record
この文書は読取専用です。文書のソースを閲覧することは可能ですが、変更はできません。もし変更したい場合は管理者に連絡してください。
====== ActiveRecord ====== RailsのO/Rマッパ&モデルオブジェクト。Railsから独立して使う事も可能。 ===== テーブル規約 ===== 調べ中というか記憶の中から引っ張り出しているので間違ってるかも テーブル名 * 単語の複数形(hoges) * hogesテーブルに対応するActiveRecordクラスはHoge * アンダースコア入りの場合 piyopiyo_hoges → PiyopiyoHoge * 多対多の中間テーブルは「テーブル1の複数形_テーブル2複数形」(hoges_fugas) カラム名 * 主キー:id * 外部キー:外部テーブル名単数形_外部テーブルの主キー名(例:fuga_id) * アンダースコアで連結したカラム名に対応するプロパティ名: そのまま hoge_date → hoge_date ===== 多対多 ===== ActiveRecordで多対多を実装する方法は2つある。 - has_and_belongs_to_many (habtm) 2つのテーブルのidのみをもつリレーション専用の中間テーブルを使う方法、モデルクラスを持たない - has_many :through リレーションに2つのテーブルのidをもつActiveRecordモデルクラスを使う <code ruby> require "rubygems" require "active_record" ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => "test.db") ActiveRecord::Schema.define do create_table :bookmarks do |t| t.column :name, :string t.column :url, :string t.column :memo, :string end create_table :tags do |t| t.colmn :name, :string end create_table :taggings do |t| t.column :bookmark_id, :integer t.column :tag_id, :integer end end # bookmarksテーブルとtagsテーブルは多対多(中間テーブルtaggings) class Bookmark < ActiveRecord::Base has_many :bookmark_tags: has_many :tags, :through => :taggings end class Tag < ActiveRecord::Base has_many :bookmark_tags: has_many :bookmarks, :through => :taggings end class Tagging < ActiveRecord::Base belongs_to :bookmark belongs_to :tag end </code> ===== Observer ===== after_saveなどのイベント駆動なメソッドを外だししたもの。実装がObserverパターンかどうかは知らん。 hogeモデルのオブサーバクラスを作成するには… $ script/generate observer hoge ===== nemed_scope ===== よくある検索条件をメソッド化できる。 <code rubyt> User < ActiveRecord::Base scope :active, where(state: "active") scope :teenager, where("age >= 13 and age <= 19") scope :male, where(gender: 1) scope :age, -> x { where(age: x) } end </code> 小さい条件をたくさん作って、メソッドチェインで組み合わせるのがポイント <code> # 15歳男性の有効なユーザを取得 active_fifteen_boys = User.active.age(15).male </code> SQLは遅延実行されるので、複数のnamed_scopeでチェインしてもSQLが実行されるのは一度だけ。 ===== ポリモーフィック・アソシエーション ===== 複数のモデルからhas_many、has_oneされるモデルを作る事ができる。 所有されるモデル <code ruby> class Attachment < ActiveRecord::Base belongs_to :attachable, :polymorphic = true end </code> 所有するモデル <code ruby> class Document < ActiveRecord::Base has_many :attachments, :as => attachable end class Mail < ActiveRecord::Base has_many :attachments, :as => attachable end </code> belongs_toの対象が複数のモデルではないので、テーブルを直指定できない。そこでattachableという仮のモデル名を付け、DocumentとMailがattachableなモデルであるとして扱う、というイメージ belongs_toの側にはattachable_idとattachable_type(typeのみでも良いようだ)というカラムが必要。 ===== 単一テーブル継承 ===== 1つのテーブルから振る舞いの異なる複数のActiveRecordクラスを作りたいときに使う。テーブルにtypeというStringのカラムを作成しておく。 例:items(商品)テーブル |id|integer|ID| |type|string|型| |name|string|商品名| |sold_from|date|販売開始日| |sold_from|date|販売終了日| で、通常商品(常に販売される)、限定商品(販売期間が限定される)で振る舞いを変えたい場合 <code ruby> class Item end class RegularItem < Item # 通常商品 # sold_fromやsold_toの値に関わらず常にtrue def on_sale? true end end class LimitedItem < Item # 限定商品 # 今日の日付がsold_fromとsold_toのときのみ販売中 def on_sale? today = Date.today sold_from <= today && sold_to >= today end end </code> このように同じテーブルを利用しながらも、振る舞いの異なるクラスを作成できる。注意点として、ItemとLimitedItemとRegularItemは別ファイルに作成する。models/item.rbにLimitedItemとRegularItemのクラスを書いても動作してくれない。 検索時は LimitedItem.all とすれば、 select * from items where type = "LimitedItem"; というSQLが生成される。typeは自動的にセットされる。また、insertでは、 RegularItem.create(:name => "オプーナ購入権") とすれば insert into items (type, name) values ("RegularItem", "オプーナ購入権") 子クラスでデータにアクセスすれば、typeカラムを意識する必要はない。親クラスのItemでinsertやupdateも可能だがtypeをプログラマが管理する必要があるのでちょっと面倒。とくに画面を作る時に死ぬほど面倒なのでLimitedItemとRegularItemそれぞれに別にControllerとViewを作成しよう。 index画面については同一画面上に複数クラスを並べるのも容易なので、ItemControllerにindexメソッドを作成することもできる。 <code ruby> class ItemsController def index @items = Item.all end end </code> としたとき、index.html.erbで <code ruby> @items.each |item| link_to "Show", item link_to "Edit", edit_item_url(item) end </code> というscaffoldが作成するコードでは、edit_item_urlが要求する型と実際の型が違うためエラーになってしまう、こういうときはpolymorphic_urlという便利メソッドを使おう。 <code ruby> @items.each |item| link_to "Show", item link_to "Edit", edit_polymorphic_url(item) end </code> edit_polymorphic_urlは実際の型を見て、RegularItemsController#editかLimitedItemsController#editに割り振る。 ===== カウンタキャッシュ ===== 一対多関連で、一側のオブジェクトから関連する多のレコードがいくつあるか調べるには、 <code ruby> hoge.fugas.count hoge.fugas.size </code> とすれば良いが、毎回countのSQLが実行されるので場合によっては非効率。 railsには多テーブルを操作したときに、一のテーブルに自動的に関連する多のレコードの数を更新するcounter_cacheという機能がある。 <code ruby> class Hoge has_many :fugas end class Fuga belongs_to :hoge, :counter_cache => true end </code> とモデルを定義して、Hogeのテーブルには fugas_count というカラムを作っておくと fuga テーブルを操作したときに自動的に関連する hoge テーブルの fugas_count カラムを修正するようになる。 カウンタキャッシュは実際にレコード数をcountしてセットしているのではなく、create時に+1、delete時に-1している。よって初期値が正しくセットしなければならない。カウンタキャッシュのデフォルト値は 0 に設定する。あとからカウンタキャッシュを追加する場合にはmigrationで現在のレコード数をカウンタキャッシュに設定する。 <code ruby> add_column :hoge, :fugas_count, :default => 0 execute "update hoge set fugas_count = (select count(*) from fuga where fuga.hoge_id = hoge.id);" </code> 当然、railsを通さずに直接レコードを追加、削除するとカウンタキャッシュの値が不正になるので注意。 ===== テーブル結合 ===== ==== eager_loadとjoin ==== eager loading(多対一リレーションなデータで、多を検索したときに一のデータもjoinして取っておく事)する <code ruby> class Book < ActiveRecord::Base belongs_to author end # 取得されたbookの数だけauthorを取得するSQLを実行(N+1クエリ問題) books = Book .where("title LIKE ?", "あ%") books.each { |book| puts book.author.name } </code> <code sql> select .. from books where (title like 'あ%'); select .. from authors where id = 1; -- booksの取得件数だけSQLがauthorを取得するSQLが実行されてしまう select .. from authors where id = 2; select .. from authors where id = 5; . . . </code> <code ruby> # authorも同時に取得(left outer joinを使用) books = Book .where("books,title LIKE ?", "あ%") .eager_load(:author) books.each { |book| puts book.author.name } </code> <code sql> select books.*, authors.* from books left outer join authors on books.author_id = authors.id where (books.title like 'あ%'); </code> left outer joinのため、 author が nil になる可能性があるので注意。inner joinで結合したい場合はjoinsを併用する <code ruby> # authorも同時に取得(inner joinを使用) books = Book .where("books.title LIKE ?", "あ%") .eager_load(:author) .joins(:author) books.each { |book| puts book.author.name } </code> <code sql> select books.*, authors.* from books inner join authors on books.author_id = authors.id where (books.title like 'あ%') </code> joinsを単体で使用するとSQL発行時にauthorsテーブルをinner joinするがauthorデータの取得は行わない。検索条件にauthorを使いたいがauthorのデータは不要な場合に使用する <code ruby> # 著者名が「あ」で始まる書籍を取得 books = Book .where("author.name LIKE ?", "あ%") .joins(:author) </code> <code sql> select books.* from books inner join authors on books.author_id = authors.id where (author.name like 'あ%') </code> ==== preload ==== preloadはjoinせず、2つのSQLを発行して関連データを取得する。 <code ruby> # 著者名が「あ」で始まる書籍を取得 books = Book .where("books.title LIKE ?", "あ%") .preload(:author) books.each { |book| puts book.author.name } </code> <code sql> select books.* from books where (books,title like 'あ%'); select authors.* from authors where authors.id in (1,2,5,...); -- 最初のSQLで取得されたレコードのauthor_idで検索 </code> ==== includes ==== includesは特殊で、通常はpreloadと同じ動きだが、結合テーブル側を検索条件に含めるとeager_loadに変わる <code ruby> books = Book .where("books,title LIKE ?", "あ%") .includes(:author) books.each { |book| puts book.author.name } </code> <code sql> select books.* from books where (books,title like 'あ%'); select authors.* from authors where authors.id in (1,2,5,...); </code> author側に条件があるとjoinする(ただしオブジェクト形式での条件指定しかできない) <code ruby> books = Book .where("books,title LIKE ?", "あ%") .includes(:author) .where(author: {birth_year: 1987}) books.each { |book| puts book.author.name } </code> <code sql> select books.*, authors.* from books left outer join authors on books.author_id = authors.id where books.title LIKE 'あ%' and authors.birth_year = 1987; </code>
rails/active_record.txt
· 最終更新:
2024/01/10 14:30
by
nullpon
ページ用ツール
文書の表示
以前のリビジョン
バックリンク
文書の先頭へ