ãRails3.2ããããããã¦ãã¦ãincludes 使ç¨æã« where 㧠ãããã使ãã¨æ³å®å¤ã«ãªãããç¥ããªã件ãã¾ãã¯ããªãRails4 ãã references ãå¿ è¦ã«ãªã£ãã®ã
çããããä¹
ãã¶ãã§ããMUGENUP ã® osada ã§ãã
ææ»ã«ãããã²ã¼ã
ã®1å¨ç®ãã¯ãªã¢ããã®ã§ãæ»ã£ã¦ã¾ããã¾ããï¼
ç®æ¨ãéæããããã«å¤§äºãªãã¨ã¯è¦³å¯å
ã§ãããã¨ãããã¨ãæãç¥ããããã²ã¼ã ã§ããã
ãã¦ãããªä»åã¯ãRails 3.2
ã® includes
ã«é¢ããããããã¦ãã¦ã®å
±æã§ãã
å¼ç¤¾ okuda ãããã©ã¤ã»ã¢ã³ãã»ã¨ã©ã¼ã®æ«ã«è¦ã¤ããããã°ã®è§£èª¬ã«ãªãã¾ãã
Rails 4 ç³»
ãã使ãã®æ¹ã«ã¯ä¸è¦ã§ãã®ã§ãä½ãã®è©±ã®ãã¿ã«ãªãã°å¹¸ãã§ãã
includes 㯠LEFT OUTER JOIN 㨠eager loading ã® 2 種é¡ããã
Rails
ã§ã¯ãincludes
㯠2種é¡ã®ä½¿ãæ¹ãã§ãã¾ãã
1. eager loading
2. LEFT OUTER JOIN
é常ã®ä½¿ãæ¹ã ã¨ã(1) ã® eager loading
ã¨ãªãã
where
å¥ã®ä¸ã§ãincludes
ãã ãã¼ãã«ãåç
§ããã¨ã
(2) ã® LEFT OUTER JOIN
ã¨ãªããã¨ããèªèã§ããã
class User < ActiveRecord::Base attr_accessible :email, :name has_many :user_items end describe User do let!(:user) { create :user, name: "user0", email: "[email protected]" } it "eager loading" do subject = User.where("`name` LIKE ?", "%user0%").explain expect(subject).to include "SELECT `users`.* FROM `users` WHERE (`name` LIKE '%user0%')" # => User Load (0.9ms) SELECT `users`.* FROM `users` WHERE (`name` LIKE '%user0%') # => UserItem Load (1.3ms) SELECT `user_items`.* FROM `user_items` WHERE `user_items`.`user_id` IN (252) end it "outer join by string" do subject = User.includes(:user_items).where("user_items.number IS NULL").explain expect(subject).to include "LEFT OUTER JOIN" # => SQL (2.0ms) SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`email` AS t0_r2, `users`.`created_at` AS t0_r3, `users`.`updated_at` AS t0_r4, `user_items`.`id` AS t1_r0, `user_items`.`user_id` AS t1_r1, `user_items`.`item_id` AS t1_r2, `user_items`.`number` AS t1_r3, `user_items`.`created_at` AS t1_r4, `user_items`.`updated_at` AS t1_r5 FROM `users` LEFT OUTER JOIN `user_items` ON `user_items`.`user_id` = `users`.`id` WHERE `user_items`.`number` IS NULL end end
ä¸ä½ä½ãåé¡ãªã®ãï¼
ä¸ã§LEFT OUTER JOIN
ã«ãªãæ¡ä»¶ã
where
å¥ã®ä¸ã§ãincludes
ãã ãã¼ãã«ãåç §ãã ã¨æ¸ããã®ã§ãããããã¯èª¤ãã§ããã
端çã«è¨ãã¨ã where å¥ã®ä¸ã§ãããããå«ã¾ãã¦ããã°ãLEFT OUTER JOIN ã¨ã¿ãªã ãã¨ãããã¨ã ã£ãã®ã§ãã
ä½ãåé¡ã«ãªãã®ã§ããããï¼
çãããwhere å¥ã« email ã使ã£ããã¨ã¯ããã¾ãããï¼
it "like email" do subject = User.includes(:user_items).where("`email` LIKE ?", "%[email protected]%").explain expect(subject).to include "LEFT OUTER JOIN" expect(subject).to include "SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`email` AS t0_r2, `users`.`created_at` AS t0_r3, `users`.`updated_at` AS t0_r4, `user_items`.`id` AS t1_r0, `user_items`.`user_id` AS t1_r1, `user_items`.`item_id` AS t1_r2, `user_items`.`number` AS t1_r3, `user_items`.`created_at` AS t1_r4, `user_items`.`updated_at` AS t1_r5 FROM `users` LEFT OUTER JOIN `user_items` ON `user_items`.`user_id` = `users`.`id` WHERE (`email` LIKE '%[email protected]%')" end
user_items
ã join
ããã¤ããããªãã®ã«ãjoin
ããã¦ãã¾ãã
email ã«ã¯ã»ã¨ãã©å¿
ãããã
ã å«ã¾ãã¾ãã
email 以å¤ã§ã¯ eager loading
ãªã®ã«ãemail 㧠where ãããé端ã«ããããªãLEFT OUTER JOIN
ã«ãªã£ã¦ãã¾ãã®ã§ãã
ç¡è«ããã¯ãemail
ã«éãã¾ãããname
ã§ããããå«ã¾ãã¦ããã°åããã¨ã§ãã
it "like name with dot" do subject = User.includes(:user_items).where("`name` LIKE ?", "%user0.name%").explain expect(subject).to include "LEFT OUTER JOIN" end
ããã«ã¯ãstring
ã§æ§ç¯ãã¦ãããããã¨ããçç±ã§ãããã¾ããã
arel_table ã§æ§ç¯ãã¦ããçµæã¯åãã§ãã
it "like name with dot on arel" do subject = User.includes(:user_items).where(User.arel_table[:name].matches("%user0.name%")).explain expect(subject).to include "LEFT OUTER JOIN" end
ãªããããªãã¨ã«ãªã£ã¦ãã¾ãã®ã§ããããï¼
ããã¯ãeager loading
㨠LEFT OUTER JOIN
ãåºå¥ãããã®ããä¸è¨ã®ã¡ã½ããã ããã§ãã
references_eager_loaded_tables?
ã§ãã©ã¡ããªã®ãã決ãã¾ããã
ãã®æ±ºå®æ¨©ãæ¡ãã®ãtables_in_string
ã§ãã
def references_eager_loaded_tables? joined_tables = arel.join_sources.map do |join| if join.is_a?(Arel::Nodes::StringJoin) tables_in_string(join.left) else [join.left.table_name, join.left.table_alias] end end joined_tables += [table.name, table.table_alias] # always convert table names to downcase as in Oracle quoted table names are in uppercase joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq (tables_in_string(to_sql) - joined_tables).any? end def tables_in_string(string) return [] if string.blank? # always convert table names to downcase as in Oracle quoted table names are in uppercase # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_'] end
string.scan
ã®ä¸ã§ãsql ã®ä¸ã«ãããããå«ã¾ãã¦ãããã©ãã
ãã®ã¿ã§å¤æãã¦ãããã¨ãè¦ã¦åããã¨æãã¾ãã
æ«å°¾ã®è¡ã pry ããçµæãä¸è¨ã§ãã
[1] pry(#<ActiveRecord::Relation>)> to_sql => "SELECT `users`.* FROM `users` WHERE (`users`.`name` LIKE '%user0.name%')" [2] pry(#<ActiveRecord::Relation>)> tables_in_string(to_sql) => ["users", "user0"] [3] pry(#<ActiveRecord::Relation>)> joined_tables => ["users"] [4] pry(#<ActiveRecord::Relation>)> (tables_in_string(to_sql) - joined_tables).any? => true
確ãã«ãããã
ã§å¤æãã¦ãããã¨ãåããã¾ãã
ã¾ã¨ã
以ä¸ããRails3.2
ã§ãincludes
ã«èµ·ãããããããã¦ãã¦ã§ãã
ç¥ã£ã¦ããã°ã©ãã¨ãããã¨ã¯ããã¾ããããæå³ããæ³å®å¤ã®çµæãç£ããã¨ãããã¾ãã®ã§ãååãæ°ãã¤ãä¸ããã
ãããããã®æ©ä¼ã«ãæè¿ãªãªã¼ã¹ãããRails4.1.0
ã«ä¹ãæããã®ãè¯ãã®ã§ã¯ãªãã§ããããã
(ãã¾ã) Rails 4.1 ã§ã¯ãLEFT OUTER JOIN ã®ä½¿ç¨ã«ãreferences ãå¿ è¦ã«ãªã£ãã
ãã¦ããããªããã§ãRails 4ããã
LEFT OUTER JOINãããæã¯æ示çã«ã
references` ãæ¸ããã¨ã«ãªãã¾ããã
User.includes(:user_items).where("user_items.number IS NULL") # => Mysql2::Error: Unknown column 'user_items.number' in 'where clause': SELECT `users`.* FROM `users` WHERE (user_items.number IS NULL) User.includes(:user_items).where("user_items.number IS NULL").references(:user_items) # => SQL (0.4ms) SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`email` AS t0_r2, `users`.`created_at` AS t0_r3, `users`.`updated_at` AS t0_r4, `user_items`.`id` AS t1_r0, `user_items`.`user_id` AS t1_r1, `user_items`.`item_id` AS t1_r2, `user_items`.`number` AS t1_r3, `user_items`.`created_at` AS t1_r4, `user_items`.`updated_at` AS t1_r5 FROM `users` LEFT OUTER JOIN `user_items` ON `user_items`.`user_id` = `users`.`id` WHERE (user_items.number IS NULL)
ä¸è¿°ã®ã¡ã½ããã¯ãä¸è¨ã®ããã«æ¸ãæããã¾ãã
def references_eager_loaded_tables? joined_tables = arel.join_sources.map do |join| if join.is_a?(Arel::Nodes::StringJoin) tables_in_string(join.left) else [join.left.table_name, join.left.table_alias] end end joined_tables += [table.name, table.table_alias] # always convert table names to downcase as in Oracle quoted table names are in uppercase joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq (references_values - joined_tables).any? end
å¤æ´ããã¦ããã®ã¯ãæ«å°¾ã® (references_values - joined_tables).any?
ã§ãã
3.2ç³»ã®ã¨ãã¯ã(tables_in_string(to_sql) - joined_tables).any?
ã§ããã
ãããã®åé¡ã«ã¤ãã¦ã¯ããã¡ãã§æ¤è¨ãããä¿®æ£ããããããã§ãã includes eager loading is realized with LEFT OUTER JOIN strategy when querying with value contains 'dot' · Issue #7177 · rails/rails
Deprecated
ã«ãªã£ããã¨ãããã¨ã§ããããä»å試ããã¨ãããã¨ã©ã¼ã«ãªãã¾ããã
確èªããã¨ãããRails 4.1
ã§ã¯ã使ç¨ä¸å¯ã«ãªã£ãããã§ãã
Remove implicit join references that were deprecated in 4.0.
(ãã¾ã 2) Arel ã使ãã
äºä¾ã説æããããã«ãwhere ã®ä¸ã«æååãæ¸ãã¾ããããsexy ã§ã¯ããã¾ããã 綺éºã«æ¸ãã¾ãããã
it "like name call outer join" do subject = User.includes(:user_items).where(user_items: { number: nil } ).explain expect(subject).to include "LEFT OUTER JOIN" end
(ãã¾ã 3)
使ç¨ãããã¹ããç½®ãã¦ããã¾ãã
- ruby_test/rails_test/rails3.2/test01/spec/models/user_spec.rb at master · osdakira/ruby_test
- ruby_test/rails_test/rails4.1.0/test01/spec/models/user_spec.rb at master · osdakira/ruby_test
åè1
SQLã®ç¢ºèªã«ãto_sql
ã§ã¯ãªãexplain
ã使ã£ã¦ããã®ã¯ãRails 3
ç³»ã®to_sql
ã¯ãeager loading
ãèæ
®ããªãããã§ãã
Rails 4
ç³»ã§ã¯ä¿®æ£ããã¦ãã¾ãã