Class: LowCardTables::HasLowCardTable::LowCardAssociation
- Inherits:
-
Object
- Object
- LowCardTables::HasLowCardTable::LowCardAssociation
- Defined in:
- lib/low_card_tables/has_low_card_table/low_card_association.rb
Overview
A LowCardAssociation represents a single association between a referring model class and a referred-to low-card model class. Note that this represents an association between classes, not between objects – that is, there is one instance of this class for a relationship from one referring class to one referred-to class, no matter how many model objects are instantiated.
Instance Attribute Summary collapse
-
#association_name ⇒ Object
readonly
Returns the name of the association – this will always have been the first arguent to
has_low_card_table
.
Instance Method Summary collapse
-
#class_method_name_to_low_card_method_name_map ⇒ Object
Returns a Hash that maps the names of methods that should be added to the referring class to the names of methods they should invoke on the low-card class.
-
#create_low_card_object_for(model_instance) ⇒ Object
Given an instance of the referring class, returns an instance of the low-card class that is configured correctly for the current value of the referring column.
-
#delegated_method_names ⇒ Object
Returns an Array of names of methods on the low-card table that should be delegated to.
-
#foreign_key_column_name ⇒ Object
Computes the correct name of the foreign-key column based on the options passed in.
-
#initialize(model_class, association_name, options) ⇒ LowCardAssociation
constructor
Creates a new instance.
-
#low_card_class ⇒ Object
Figures out what the low-card class this association should use is; this uses convention, with some overrides.
-
#update_collapsed_rows(collapse_map, collapsing_update_scheme) ⇒ Object
When a low-card table has a column removed, it will typically have duplicate rows; these duplicate rows are then deleted.
-
#update_foreign_key!(model_instance) ⇒ Object
Updates the foreign key for this association on the given model instance.
Constructor Details
#initialize(model_class, association_name, options) ⇒ LowCardAssociation
Creates a new instance. model_class is the Class (which must inherit from ActiveRecord::Base) that is the referring model; association_name is the name of the association. options can contain any of the options accepted by LowCardTables::HasLowCardTables::Base#has_low_card_table.
14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/low_card_tables/has_low_card_table/low_card_association.rb', line 14 def initialize(model_class, association_name, ) @model_class = model_class @association_name = association_name.to_s @options = .with_indifferent_access # We call this here so that if things are configured incorrectly, you'll get an exception at the moment you # try to associate the tables, rather than at runtime when you try to actually use them. Blowing up early is # good. :) foreign_key_column_name low_card_class.low_card_referred_to_by(model_class) end |
Instance Attribute Details
#association_name ⇒ Object (readonly)
Returns the name of the association – this will always have been the first arguent to has_low_card_table
.
9 10 11 |
# File 'lib/low_card_tables/has_low_card_table/low_card_association.rb', line 9 def association_name @association_name end |
Instance Method Details
#class_method_name_to_low_card_method_name_map ⇒ Object
Returns a Hash that maps the names of methods that should be added to the referring class to the names of methods they should invoke on the low-card class. This takes into account both the :delegate
option (via its internal call to #delegated_method_names) and the :prefix
option.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/low_card_tables/has_low_card_table/low_card_association.rb', line 30 def class_method_name_to_low_card_method_name_map return { } if .has_key?(:delegate) && (! [:delegate]) out = { } delegated_method_names.each do |column_name| desired_method_name = case [:prefix] when true then "#{association_name}_#{column_name}" when String, Symbol then "#{[:prefix]}_#{column_name}" when nil then column_name else raise ArgumentError, "Invalid :prefix option: #{[:prefix].inspect}" end out[desired_method_name] = column_name out[desired_method_name + "="] = column_name + "=" end out end |
#create_low_card_object_for(model_instance) ⇒ Object
Given an instance of the referring class, returns an instance of the low-card class that is configured correctly for the current value of the referring column.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/low_card_tables/has_low_card_table/low_card_association.rb', line 83 def create_low_card_object_for(model_instance) ensure_correct_class!(model_instance) id = get_id_from_model(model_instance) out = nil if id template = low_card_class.low_card_row_for_id(id) out = template.dup out.id = nil out else out = low_card_class.new end out end |
#delegated_method_names ⇒ Object
Returns an Array of names of methods on the low-card table that should be delegated to. This may be different than the names of methods on the referring class, because of the :prefix option.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/low_card_tables/has_low_card_table/low_card_association.rb', line 52 def delegated_method_names value_column_names = low_card_class.low_card_value_column_names.map(&:to_s) if .has_key?(:delegate) && (! [:delegate]) [ ] elsif [:delegate].kind_of?(Array) || [:delegate].kind_of?(String) || [:delegate].kind_of?(Symbol) out = Array([:delegate]).map(&:to_s) extra = out - value_column_names if extra.length > 0 raise ArgumentError, "You told us to delegate the following methods to low-card class #{low_card_class}, but that model doesn't have these columns: #{extra.join(", ")}; it has these columns: #{value_column_names.join(", ")}" end out elsif [:delegate] && [:delegate].kind_of?(Hash) && [:delegate].keys.map(&:to_s) == %w{except} excluded = ([:delegate][:except] || [:delegate]['except']).map(&:to_s) extra = excluded - value_column_names if extra.length > 0 raise ArgumentError, "You told us to delegate all but the following methods to low-card class #{low_card_class}, but that model doesn't have these columns: #{extra.join(", ")}; it has these columns: #{value_column_names.join(", ")}" end value_column_names - excluded elsif (! .has_key?(:delegate)) || [:delegate] == true value_column_names else raise ArgumentError, "Invalid value for :delegate: #{[:delegate].inspect}" end end |
#foreign_key_column_name ⇒ Object
Computes the correct name of the foreign-key column based on the options passed in.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/low_card_tables/has_low_card_table/low_card_association.rb', line 102 def foreign_key_column_name @foreign_key_column_name ||= begin out = [:foreign_key] unless out out = "#{@model_class.name.underscore}_#{association_name}" out = $1 if out =~ %r{/[^/]+$}i out = out + "_id" end out = out.to_s if out.kind_of?(Symbol) column = model_class.columns.detect { |c| c.name.strip.downcase == out.strip.downcase } unless column raise ArgumentError, %{You said that #{model_class} has_low_card_table :#{association_name}, and we have a foreign-key column name of #{out.inspect}, but #{model_class} doesn't seem to have a column named that at all. Did you misspell it? Or perhaps something else is wrong? The model class has these columns: #{model_class.columns.map(&:name).sort.join(", ")}} end out end end |
#low_card_class ⇒ Object
Figures out what the low-card class this association should use is; this uses convention, with some overrides.
By default, for a class User that has_low_card_table :status
, it looks for a class UserStatus. This is intentionally different from Rails’ normal conventions, where it would simply look for a class Status. This is because low-card tables are almost always unique to their owning table – i.e., the case where multiple tables say has_low_card_table
to the same low-card table is very rare. (This is just because having multiple tables that have – and always will have – the same set of low-card attributes is also quite rare.) Hence, we use a little more default specificity in the naming.
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/low_card_tables/has_low_card_table/low_card_association.rb', line 174 def low_card_class @low_card_class ||= begin # e.g., class User has_low_card_table :status => UserStatus out = [:class] || "#{model_class.name.underscore.singularize}_#{association_name}" out = out.to_s if out.kind_of?(Symbol) out = out.camelize if out.kind_of?(String) if out.kind_of?(String) begin out = out.constantize rescue NameError => ne raise ArgumentError, %{You said that #{model_class} has_low_card_table :#{association_name}, and we have a :class of #{out.inspect}, but, when we tried to load that class (via #constantize), we got a NameError. Perhaps you misspelled it, or something else is wrong? NameError: (#{ne.class.name}): #{ne.}} end end unless out.kind_of?(Class) raise ArgumentError, %{You said that #{model_class} has_low_card_table :#{association_name} with a :class of #{out.inspect}, but that isn't a String or Symbol that represents a class, or a valid Class object itself.} end unless out.respond_to?(:is_low_card_table?) && out.is_low_card_table? raise ArgumentError, %{You said that #{model_class} has_low_card_table :#{association_name}, and we have class #{out} for that low-card table (which is a Class), but it either isn't an ActiveRecord model or, if so, it doesn't think it is a low-card table itself (#is_low_card_table? returns false). Perhaps you need to declare 'is_low_card_table' on that class?} end out end end |
#update_collapsed_rows(collapse_map, collapsing_update_scheme) ⇒ Object
When a low-card table has a column removed, it will typically have duplicate rows; these duplicate rows are then deleted. But then referring tables need to be updated. This method gets called at that point, with a map of <winner row> => <array of loser rows>, and the collapsing_update_scheme
declared by this referring model class. It is responsible for handling whatever collapsing update scheme has been declared properly.
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/low_card_tables/has_low_card_table/low_card_association.rb', line 131 def update_collapsed_rows(collapse_map, collapsing_update_scheme) if collapsing_update_scheme.respond_to?(:call) collapsing_update_scheme.call(collapse_map) elsif collapsing_update_scheme == :none # nothing to do else row_chunk_size = collapsing_update_scheme current_id = @model_class.order("#{@model_class.primary_key} ASC").first.id while true current_id = update_collapsed_rows_batch(current_id, row_chunk_size, collapse_map) break if (! current_id) end end end |
#update_foreign_key!(model_instance) ⇒ Object
Updates the foreign key for this association on the given model instance. This is called by LowCardTables::HasLowCardTable::Base#low_card_update_foreign_keys!, which is primarily invoked by a :before_save
filter and alternatively can be invoked manually.
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/low_card_tables/has_low_card_table/low_card_association.rb', line 150 def update_foreign_key!(model_instance) hash = { } low_card_object = model_instance._low_card_objects_manager.object_for(self) low_card_class.low_card_value_column_names.each do |value_column_name| hash[value_column_name] = low_card_object[value_column_name] end new_id = low_card_class.low_card_find_or_create_ids_for(hash) unless get_id_from_model(model_instance) == new_id set_id_on_model(model_instance, new_id) end end |