ãªãµã¼ãã»ã¢ã³ãã»ã¤ããã¼ã·ã§ã³ ã®æ¨ªå±±ã§ãã
ã¢ã¦ããããããã¼ã£ã¦ãã¼ã£ã¦ãã5å¹´ã»ã©ã«ãªãã¾ããããã£ããããã¡ãã¢ããã³ãã«ã¬ã³ãã¼ ã£ã¦ãããªããã§ããï¼ãã¨å£èµ°ã£ã¦ãã¾ã£ãããè¨ãåºãã£ãºã®æ³å ã§è¨äºãæ¸ããã¨ã«ãªãã¾ããã
ãã°ããã®éãä»ãåããã ããã
èªå·±ç´¹ä»
10年以ä¸æµãã®ã¨ã³ã¸ãã¢ããã¦ããã¾ãã
ãããããªä¼ç¤¾ã§åãã¦ãã¾ããããããRNIã¯ãé
好ããªäººãå¤ãã®ã§ãããããã¨ããããã¦è²°ããã®ã§ãªããªãå±
å¿å°ãè¯ããæ°ãä»ãã°5å¹´ã»ã©ãä¸è©±ã«ãªã£ã¦ãã¾ãã
Rails2.0ã®é ããrails ã§ä»äºããã¦ããã趣å³ã§æ¸ãã¦ããé ãå«ããã¨ruby æ´ã¯20年以ä¸ã«ãªãã§ããããã
ããããªæ¬ãæã£ã¦ãã¾ããããããªã ã£ãã®ç¥ã£ãã®ã¯æè¿ã§ããã©ã
ããã°ã©ãã³ã°è¨èª Ruby (2009)
rails ã¢ããã°ã¬ã¼ãã¸ã®é·ãéã®ã
ãã¦ã稼åä¸ã®ãµã¼ãã¹ããããã§ãããå¼ç¤¾ã®ãµã¼ãã¹ãä¸é¨å¤ããã¼ã¸ã§ã³ã®ruby ãrails ã§åãã¦ãã¾ãã
ã¢ããã°ã¬ã¼ããããã¨æãã¤ã¤ãæè¿ã¾ã§äººæä¸è¶³ã§æãåºãã¾ããã§ããããä½è£ãã§ããã®ã§ä»å¹´å¤§ããåãå§ãã¾ããã
æãæ¸å¿µã¨ãªã£ã¦ããã®ãrails5.1ã§åãã¦ãããµã¼ãã¹ã§ãã
ruby2.xãEOLã¨ãªã£ãã®ã§ï¼ãªãåããã¢ããã°ã¬ã¼ãã¯å§ãã¦ããã®ã§ããï¼ä¸å»ãæ©ãã¢ããã°ã¬ã¼ãããªãã¦ã¯ãªãã¾ããã
ä¸æãæã¿ã¤ã¤1å¹´è¿ãããã¦ããããçµãããè¦ãã¦ãã¾ããããéä¸ã§ã¶ã¤ãã£ãæã大ããªå£ã«ã¤ãã¦æ¸ãã¦ã¿ã¾ãã
èæ¯
åé¡ã®ãµã¼ãã¹ã¯(æ´å²ççµç·¯ãã)å¥ã®ãµã¼ãã¹ã¨å¯ã«çµåãã¦ãããåãRDBMS ä¸ã«2ã¤ã®ãã¼ã¿ãã¼ã¹ãç½®ãã¦ç¸äºã«èªã¿æ¸ãã§ããããã«ãã¦ãã¾ãã
以å¾ã¯ããããµã¼ãã¹Alphaããµã¼ãã¹Bravoã¨ãã¾ãã
Alphaã®ActiveRecord ã®ã¯ã©ã¹ã¯ç´ ç´ã«å®è£
ããã¦ãããåºåºã¯ã©ã¹ã§ãã¼ã¿ãã¼ã¹åã¨ãã¦bravoãä»å ãããã¼ãã«åã«alphaãä»å ããããã«ãã¦ãã以å¤ãç¹ã«æèããå©ç¨ã§ããããã«ãªã£ã¦ãã¾ãã
class Bravo < ApplicationRecord
self .abstract_class = true
establish_connection :"bravo_ #{ Rails .env} "
class << self
private
def database_name
Rails .configuration.database_configuration[" bravo_ #{ Rails .env}" ][' database ' ]
end
end
end
class Company < Bravo
self .table_name = "#{ database_name} .alpha_company "
...
end
BravoããAlphaã®ã¯ã©ã¹ã使ãæã¯ãNamespaceã«Alpha::ãä»å ãã¦ãã¾ãã
module Alpha
def self .table_name_prefix
' alpha_ '
end
end
class Alpha ::Company < ActiveRecord ::Base
...
end
ããã§åå空é ãåããç¶æ
ã§å
±æããã¢ãã«ãæ±ãã¾ãã
polymorphicé¢é£
ãã®æ§é ã§belongs_toãhas_manyãªã©rails ã®é¢é£ã¯ãã¹ã¦ééçã«ä½¿ããã®ã§ãããpolymorphicé¢é£ã ãåé¡ãããã¾ãã
ããããã«ãªãã¾ãããpolymorphicé¢é£ã¨ã¯é¢é£ä»ããããã¢ãã«ã®ã¯ã©ã¹ãéå®ãããè¦å®ã®ã«ã©ã ãç¨æãããã¨ã§é¢é£å
ã®ã¢ãã«ãéå®ããã«é¢é£ä»ããããä»çµã¿ã®ãã¨ã§ãã
ä¾ãã°Memberã¨ããã¯ã©ã¹ãCompanyã¾ãã¯Partnerã¯ã©ã¹ã«é¢é£ä»ããããä»çµã¿å ´åã¯ä»¥ä¸ã®ããã«ãã¾ãã
class Company < ApplicaionRecord
has_many :members , as : :employer
end
class Partner < ApplicaionRecord
has_many :members , as : :employer
end
class Member < ApplicaionRecord
belongs_to :employer , polymorphic : true
end
Memberã¢ãã«ã«employer_id, employer_typeã¨ããã«ã©ã ãç¨æããemployer_typeã«é¢é£ä»ããã¢ãã«ã®ã¯ã©ã¹(Companyã¾ãã¯Partner)ãå
¥ããã¨ãããããã®ã¯ã©ã¹ã«é¢é£ä»ããããpartner.employerã§å¼ã³åºããã¨ãã§ãã¾ãã
åå空é ãä»ä¸ããã¯ã©ã¹ã®polymorphicé¢é£
polymorphicé¢é£ã¯ã¯ã©ã¹åãæå®ããå¿
è¦ããããããåè¿°ã®namespaceä»å ã¨çµã¿åãããã¨æ£ããåä½ãã¾ããã
ããã§Alphaå´ã«ä»¥ä¸ã®ãããªã³ã¼ããæ¸ããã¦ãã¾ããã
(concerns以ä¸ã«ç½®ãããå¿
è¦ãªã¢ãã«ã§includeãã¦ãã)
module PolymorphicBravo
module ClassMethods
def has_many_polymorphic_bravo (relation_name, as :, **options)
class_names = [name, " Alpha:: #{ name}" ]
has_many relation_name, -> { where("#{ as} _type " => class_names) },
options.merge(foreign_key : "#{ as} _id " )
end
def has_one_polymorphic_bravo (relation_name, as :, **options)
class_names = [name, " Alpha:: #{ name}" ]
has_one relation_name, -> { where("#{ as} _type " => class_names) },
options.merge(foreign_key : "#{ as} _id " )
end
end
end
has_many_polymorphic_bravo
ã宣è¨ãããã¨
has_manyãå®ç¾©ãã
has_manyã®scopeã¨ãã¦typeã«ã©ã ã« é常ã®ã¯ã©ã¹å or Alpha::ã®ã¤ããã¯ã©ã¹å
ã®æ¡ä»¶ãå ãã
foreign_keyãè¨å®ãã
ã¨ããåä½ããã¾ãã
rails ã¢ããã°ã¬ã¼ãã§çºçããç¾è±¡
ãã¦ãããããè¸ã¾ããä¸ã§ãruby ã¨rails ã交äºã«ã¢ããã°ã¬ã¼ããã¦è¡ãã¾ããããrails6.0ã«ã¢ããã°ã¬ã¼ãããã¨ããã§ä¸è¨ã®polymorphic_bravoãåä½ããªããªãã¾ããã
å
·ä½çã«ã¯
class Company
has_many_polymorphic_bravo :members , as : :employer
end
class Member < ApplicaionRecord
belongs_to :employer , polymorphic : true
end
ãã®ãããªã¢ãã«ã§ company.members.build
ããå ´åã«ã employer_id: company.id, employer_type: 'Alpha::Company'
ãªMemberã¤ã³ã¹ã¿ã³ã¹ ãçæãããªããã°ãªãã¾ããããrails6ã§ã¯ employer_id: company.id, employer_type: nil
ã®ã¤ã³ã¹ã¿ã³ã¹ ãçæããã¾ãã
ãããããªãtypeã«ã©ã ã«å¤ãå
¥ã(å
¥ã£ã¦ãã)ã®ãï¼
åé¡è§£æ±ºã®ããã«rails ã®ã³ã¼ãã追ã£ããã¾ãã
has_manyã«scopeãæå®ãã¦ããå ´åãbuildã§scopeã«åã£ãå¤ãå
¥ãã¾ããããã®ä»çµã¿ã¯ä»¥ä¸ã®ãããªãã®ã§ããã
(以ä¸ãrails5.0-stableã®ã³ã¼ããä¾ã«åãã¾ã)
company.members#build
㯠ActiveRecord::Associations::CollectionProxy
ã§å®ç¾©ããã¦ããã
https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/associations/collection_proxy.rb#L292
def build (attributes = {}, &block)
@association .build(attributes, &block)
end
@association
㯠ActiveRecord::Associations::HasManyAssociation
ã®ã¤ã³ã¹ã¿ã³ã¹ ã§ãã
ActiveRecord::Associations::HasManyAssociation#build
ã¯ActiveRecord::Associations::CollectionAssociation
ã§å®ç¾©ããã¦ãã¾ãã
https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/associations/collection_association.rb
def build (attributes = {}, &block)
if attributes.is_a?(Array )
attributes.collect { |attr| build(attr, &block) }
else
add_to_target(build_record(attributes)) do |record|
yield (record) if block_given?
end
end
end
build_record
ãããããã¾ã«æªããã§ãã
ãã®ã¡ã½ããã¯ç¶æ¿å
ã® ActiveRecord::Associations::Association
ã¯ã©ã¹ã§å®ç¾©ããã¦ãã¾ãã
https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/associations/association.rb
def build_record (attributes)
reflection.build_association(attributes) do |record|
initialize_attributes(record, attributes)
end
end
initialize_attributes
ãåã¯ã©ã¹ã
def initialize_attributes (record, except_from_scope_attributes = nil )
except_from_scope_attributes ||= {}
skip_assign = [reflection.foreign_key, reflection.type].compact
assigned_keys = record.changed
assigned_keys += except_from_scope_attributes.keys.map(&:to_s )
attributes = create_scope.except(*(assigned_keys - skip_assign))
record.assign_attributes(attributes)
set_inverse_instance(record)
end
ã¯ãã create_scope
ãæªããã§ããã
ãã¡ã㯠ActiveRecord::Associations::CollectionAssociation
ã§å®ç¾©ããã¦ãã¾ãã
https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/associations/collection_association.rb
def create_scope
scope.scope_for_create.stringify_keys
end
scopeã¯åã¯ã©ã¹ã§
def scope
scope = super
scope.none! if null_scope?
scope
end
super
㯠ActiveRecord::Associations::Association#scope
ãªã®ã§
def scope
target_scope.merge!(association_scope)
end
target_scopeã¯åã¯ã©ã¹ã
def target_scope
AssociationRelation .create(klass, klass.arel_table, klass.predicate_builder, self ).merge!(klass.all)
end
ãªãã ãè¤éãªã³ã¼ãã ãã©ããã¨ãããã AssociationRelation
ã®ã¤ã³ã¹ã¿ã³ã¹ ãè¿ãã¦ãããã¨ãåããã¾ãã
create_scope
ã«æ»ã£ã¦ AssociationRelation#scope_for_create
ãæ¢ãã¨ãç¶æ¿å
ã® Relation
ã¯ã©ã¹ã«å®ç¾©ããã¦ãã¾ããã
https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/relation.rb
def scope_for_create
@scope_for_create ||= where_values_hash.merge(create_with_value)
end
where_values_hash
ã¯åã¯ã©ã¹ã
def where_values_hash (relation_table_name = table_name)
where_clause.to_h(relation_table_name)
end
where_clause
㯠includeãããã¢ã¸ã¥ã¼ã« QueryMethods
ã§ã¡ã¿ããã°ã©ãã³ã° ããã¦ãã¾ãã
https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/relation/query_methods.rb
Relation ::CLAUSE_METHODS .each do |name|
class_eval <<-CODE , __FILE__ , __LINE__ + 1
def #{ name} _clause # def where_clause
@values[: #{ name} ] || new_ #{ name} _clause # @values[:where] || new_where_clause
end # end
#
def #{ name} _clause=(value) # def where_clause=(value)
assert_mutability! # assert_mutability!
@values[: #{ name} ] = value # @values[:where] = value
end # end
CODE
end
ã¨ããããç½®ãã¨ãã¦ã to_h
ã¯ä¸è¦çµã¿è¾¼ã¿ã¡ã½ããã ãã©ãrequireããã ActiveRecord::Relation::WhereClause
ã§ãªã¼ãã©ã¤ãããã¦ãã¾ãã
https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/relation/where_clause.rb
def to_h (table_name = nil )
equalities = predicates.grep(Arel ::Nodes ::Equality )
if table_name
equalities = equalities.select do |node|
node.left.relation.name == table_name
end
end
binds = self .binds.map { |attr| [attr.name, attr.value] }.to_h
equalities.map { |node|
name = node.left.name
[name, binds.fetch(name.to_s) {
case node.right
when Array then node.right.map(&:val )
when Arel ::Nodes ::Casted , Arel ::Nodes ::Quoted
node.right.val
end
}]
}.to_h
end
詳細ã¯çãã¾ãããããã§scopeã«ä¸ããããwhereç¯ãããã·ã¥ã«å¤æããã scope_for_create.stringify_keys
ã«æ¸¡ã£ã¦buildãããã¢ãã«ã®attributesã«è¿½å ããã¦ãã¾ãã
rails6ã§åä½ããªãçç±
rails6ã§ã¯ä¸è¨ã® scope_for_create
ã®ã¨ãããå°ãéã£ã¦
https://github.com/rails/rails/blob/6-1-stable/activerecord/lib/active_record/relation.rb
def scope_for_create
hash = where_clause.to_h(klass.table_name, equality_only : true )
create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
hash
end
ã¨ãªã£ã¦ãã¾ãã
ActiveRecord::Relation::WhereClause#to_h
ãå¤æ´ãã
def to_h (table_name = nil , equality_only : false )
equalities(predicates, equality_only).each_with_object({}) do |node, hash|
next if table_name&.!= node.left.relation.name
name = node.left.name.to_s
value = extract_node_value(node.right)
hash[name] = value
end
ã¨ãªãã¾ããã
equalities
ã¯ã¡ã½ããã«åãåºãã
def equalities (predicates, equality_only)
equalities = []
predicates.each do |node|
if equality_only ? Arel ::Nodes ::Equality === node : equality_node?(node)
equalities << node
elsif node.is_a?(Arel ::Nodes ::And )
equalities.concat equalities(node.children, equality_only)
end
end
equalities
end
ã«ãªã£ã¦ãã¾ãã
詳細ã¯çãã¾ãããwhere_clauseãã¼ãã Arel::Nodes::Equality
ããã㯠Arel::Nodes::And
以å¤ã¯æ¡ä»¶ã«å
¥ããªãããã«ãªã£ã¦ãã¾ãã
predicates
㯠Arel::Nodes::*
ã¯ã©ã¹ã®é
åã§ã ã¯ã¨ãªãæ§é ä½ã«ãããã®ãå
¥ã£ã¦ãã¾ãã
ããã¦ã é常ã®ï¼=ã§æ¯è¼ããï¼WHEREæ¡ä»¶ã¯ Arel::Nodes::Equality
ã¯ã©ã¹ã§ããã INå¥ ã¯ Arel::Nodes::Casted
ã¯ã©ã¹ã«ãªãã¾ãã
ã¤ã¾ããrails5ã§ã¯ Arel::Nodes::Casted
ã¯ã©ã¹ãscope_for_createã®ããã·ã¥ã«æ ¼ç´ããã¦ãã¾ãããã 6ã§ã¯å¼¾ãããããã«ãªãã¾ããã
ãããbuildã§typeã«ã©ã ãnil ã«ãªãåå ã§ããã
ã©ããã¦ãããªã£ãï¼
ãã°ä¿®æ£ ã§ãã
ãã®ä¿®æ£ãå
¥ã£ãã®ã¯ PR#41319 ã
If a scope has IN cluase, scope_for_create which is passed to assign_attributes will include array values, and it will cause weird behaviors.
大å¤ããã£ã¨ã ã§ããã ã£ã¦ããããããã
INå¥ã«ä¸ããããé
åãããã·ã¥ã®å¤ã¨ãã¦æ ¼ç´ãããçµæãé
åã®æåã®å¤ãåæå¤ã¨ãã¦ä¸ãããã¦ãã¾ããã
ã¤ã¾ã has_many_polymorphic_bravo
ã¯ãã®ãã°ã«ãã£ã¦ä¸è¦æ£å¸¸ã«åä½ãã¦ããã«éããªãã£ãã®ã§ãã
ãã§ãããã§ããã
ãã§ãããªãããããåå®è£
ããã®ã¯å°é£ã極ããçµå±ãã¼ã¿æ§é ã大å¹
ã«å¤æ´ãã¦rails æ¨æºã«è¿ã¥ãããã¨ã«ãªãã¾ããã
ã¢ã³ãã¼ãããã¯ã¢ããã°ã¬ã¼ãã®æµãªã®ã§ããã¾ãããã
ä½è«1 ã¨ããã§ãã®ã³ç¬ ãã£ã¦ï¼
ä½è«ã§ããããã®ããã« ã©ã¡ãã§ãåä½ããããã«é
æ
®ãã¦ãã¾ã ãã¨ãå人çã« ã®ã³ç¬ åé¡ ã¨å¼ãã§ãã¾ãã
ããã©ããã ã㧠ã®ã³å¤ª ãã太ãã®æ¼¢åã®ç¹ãä¸ã ã£ããä¸ã ã£ããåãããªããªãã両æ¹ã«æã£ã ã¨ããæ
äº(ã¦ãã¨ãè«ã³ããã¯ã¹ 23å·»ãéè¦ã·ã¼ã«ã§å¤§ãã³ãã)ã«ç±æ¥ãã¾ãã
å¤åä»ã«èª°ãå¼ãã§ãªãã¨æãã
ããã°ã©ãã³ã°ãããæã«ã¯ææ§ãã¯ã§ããã ãæé¤ããã¹ãã§ãããã¨ããæè¨ã§ããã
ä½è«2 調æ»æ¹æ³
ç¶æ¿ãã¡ã¿ããã°ã©ãã³ã° ãå¤ç¨ãããrails ã§ã¯ãã³ã¼ãã追ã£ã¦ããã®ãé£ããã§ããã
ä»åã¯rails console+pry byebugãæ´»ç¨ãã¦è¿½è·¡ãã¾ããã
ä¾ãã°rails consoleä¸ã§ä»¥ä¸ã®ããã«ãã¦ãã³ã³ããã¹ããåãæ¿ãã¦æ·±ãæ½ã£ã¦ãããã¨ãã§ãã¾ãã
> company = company.new
> cd company #=> ã³ã³ããã¹ããcompanyã«åãæ¿ãã
> members #=> company.membersãè¿ã
> @name #=> ã¤ã³ã¹ã¿ã³ã¹å¤æ°ã®ä¸èº«ãè¦ãã
ã¡ã½ããã®å®ç¾©ãæ¢ãã«ã¯ä»¥ä¸ã®ããã«ãã¾ãã
> company.members.method(:build).source_location #=> .../versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-5.0.7.2/lib/active_record/associations/collection_association.rb"
ããããä¸æã使ãã°ãå¤æ°ãã¡ã½ããã®æ»ãå¤ãåç
§ãã¤ã¤ã³ã¼ãã追ã£ã¦ãããã¨ãã§ãã¾ãã
ã¾ããdebug gemã使ãã°ã³ã¼ãã«ãã¬ã¼ã¯ãã¤ã³ã ãåãè¾¼ã¾ãã¨ããã³ãã³ãã©ã¤ã³ ãªãã·ã§ã³ã§ä»»æã®å ´æã«ãã¬ã¼ã¯ãã¤ã³ã ãä»è¾¼ããã¨ãã§ãã¾ããrails ã®ä»»æã®å°ç¹ã«æ½ã£ã¦ãããã¨ãã§ããã®ã§é常ã«ä¾¿å©ã§ãã
ããããã使ãããã«ã¯ruby3.0+(2.7ã«ãããã¯ãã¼ãããã¦ãã¾ã)ã«ã¢ããã°ã¬ã¼ãããªããã°ãªããªãã®ã§ãåµãå
ãé¶ãå
ãåé¡ã«ãªã£ã¦ãã¾ããä»åã¯ä½¿ãã¾ããã§ããã