Class: Hold::Sequel::PropertyMapper::ManyToMany
- Inherits:
-
Hold::Sequel::PropertyMapper
- Object
- Hold::Sequel::PropertyMapper
- Hold::Sequel::PropertyMapper::ManyToMany
- Defined in:
- lib/hold/sequel/property_mapper/many_to_many.rb
Overview
Maps to an array of associated objects stored in another repo, where a :join_table exists with columns for:
- our id property (:left_key)
- other repo's id property (:right_key)
- order position within the list, starting from 0 (:order_column)
By default these properties aren’t writeable - when they are writeable:
(for now at least) the rows of the join table are owned and managed soley by the parent objects via this mapper. The associated objects themselves, however, are free-floating and are not touched during create/update/delete (except optionally to store_new any new ones on create of the parent object, when :auto_store_new => true).
If you supply a hash as :filter, this will be used to filter the join table, and will also be merged into any rows inserted into the join table. So if you use it on a writeable property, it needs to be map columns just to values rather than to other sql conditions.
NB: for now this does not assume (or do anything special with respect to) the presence of a reciprocal
many-to_many property on the target repo. This functionality will need adding later to help figure out
the side-effects of changes to a many-to-many property when it comes to cache invalidation, and to
ensure that the order given by the order_column is not upset by updates to the corresponding reciprocal
property.
So:
- Rows are inserted into the join table after the parent object is created
- Rows in the join table are nuked and re-inserted after this property on the parent object is updated
- Rows in the join table are deleted before the parent object is deleted (unless :manual_cascade_delete
=> false is specified hinting that ON CASCADE DELETE is set on the foreign key so we needn't bother)
Instance Attribute Summary collapse
-
#auto_store_new ⇒ Object
readonly
Returns the value of attribute auto_store_new.
-
#distinct ⇒ Object
readonly
Returns the value of attribute distinct.
-
#filter ⇒ Object
readonly
Returns the value of attribute filter.
-
#join_table ⇒ Object
readonly
Returns the value of attribute join_table.
-
#left_key ⇒ Object
readonly
Returns the value of attribute left_key.
-
#manual_cascade_delete ⇒ Object
readonly
Returns the value of attribute manual_cascade_delete.
-
#model_class ⇒ Object
readonly
Returns the value of attribute model_class.
-
#order_column ⇒ Object
readonly
Returns the value of attribute order_column.
-
#right_key ⇒ Object
readonly
Returns the value of attribute right_key.
-
#target_repo ⇒ Object
Returns the value of attribute target_repo.
-
#writeable ⇒ Object
readonly
Returns the value of attribute writeable.
Attributes inherited from Hold::Sequel::PropertyMapper
#property, #property_name, #repository
Class Method Summary collapse
Instance Method Summary collapse
-
#add_denormalized_columns_to_join_table_row(entity, value, row) ⇒ Object
this is a hook for you to override.
- #delete_join_table_rows(id) ⇒ Object
-
#get_many_by_member(member) ⇒ Object
find all instances in this repo whose value for this property contains the given member instance.
-
#initialize(repo, property_name, options, &block) ⇒ ManyToMany
constructor
A new instance of ManyToMany.
- #insert_join_table_rows(entity, id, values) ⇒ Object
- #load_value(row, id, properties = nil) ⇒ Object
-
#load_values(rows, ids = nil, properties = nil, &b) ⇒ Object
efficient batch load for the non-lazy case.
- #post_insert(entity, rows, insert_id) ⇒ Object
- #post_update(entity, update_entity, rows, result_from_pre_update = nil) ⇒ Object
- #pre_delete(entity) ⇒ Object
Methods inherited from Hold::Sequel::PropertyMapper
#build_insert_row, #build_update_row, #columns_aliases_and_tables_for_select, #make_filter, #make_multi_filter, #post_delete, #pre_insert, #pre_update
Constructor Details
#initialize(repo, property_name, options, &block) ⇒ ManyToMany
Returns a new instance of ManyToMany.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 40 def initialize(repo, property_name, , &block) super(repo, property_name, &nil) @join_table = [:join_table] || :"#{repo.main_table}_#{property_name}" @left_key = [:left_key] || :"#{repo.main_table.to_s.singularize}_id" @right_key = [:right_key] || :"#{property_name.to_s.singularize}_id" @qualified_left_key = Sequel::SQL::QualifiedIdentifier.new(@join_table, @left_key) @qualified_right_key = Sequel::SQL::QualifiedIdentifier.new(@join_table, @right_key) @filter = [:filter] @join_table_dataset = @repository.db[@join_table] @distinct = [:distinct] || false @order_column = [:order_column] and @qualified_order_column = Sequel::SQL::QualifiedIdentifier.new(@join_table, @order_column) @writeable = [:writeable] || false @manual_cascade_delete = [:manual_cascade_delete] != false @auto_store_new = [:auto_store_new] || false @model_class = [:model_class] or raise ArgumentError # in case you want to override anything on the instance: instance_eval(&block) if block end |
Instance Attribute Details
#auto_store_new ⇒ Object (readonly)
Returns the value of attribute auto_store_new.
37 38 39 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 37 def auto_store_new @auto_store_new end |
#distinct ⇒ Object (readonly)
Returns the value of attribute distinct.
37 38 39 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 37 def distinct @distinct end |
#filter ⇒ Object (readonly)
Returns the value of attribute filter.
37 38 39 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 37 def filter @filter end |
#join_table ⇒ Object (readonly)
Returns the value of attribute join_table.
37 38 39 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 37 def join_table @join_table end |
#left_key ⇒ Object (readonly)
Returns the value of attribute left_key.
37 38 39 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 37 def left_key @left_key end |
#manual_cascade_delete ⇒ Object (readonly)
Returns the value of attribute manual_cascade_delete.
37 38 39 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 37 def manual_cascade_delete @manual_cascade_delete end |
#model_class ⇒ Object (readonly)
Returns the value of attribute model_class.
37 38 39 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 37 def model_class @model_class end |
#order_column ⇒ Object (readonly)
Returns the value of attribute order_column.
37 38 39 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 37 def order_column @order_column end |
#right_key ⇒ Object (readonly)
Returns the value of attribute right_key.
37 38 39 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 37 def right_key @right_key end |
#target_repo ⇒ Object
Returns the value of attribute target_repo.
35 36 37 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 35 def target_repo @target_repo end |
#writeable ⇒ Object (readonly)
Returns the value of attribute writeable.
37 38 39 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 37 def writeable @writeable end |
Class Method Details
.setter_dependencies_for(options = {}) ⇒ Object
30 31 32 33 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 30 def self.setter_dependencies_for(={}) features = [*[:model_class]].map {|klass| [:get_class, klass]} {:target_repo => [IdentitySetRepository, *features]} end |
Instance Method Details
#add_denormalized_columns_to_join_table_row(entity, value, row) ⇒ Object
this is a hook for you to override
133 134 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 133 def add_denormalized_columns_to_join_table_row(entity, value, row) end |
#delete_join_table_rows(id) ⇒ Object
136 137 138 139 140 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 136 def delete_join_table_rows(id) filters = {@left_key => id} filters.merge!(@filter) if @filter @join_table_dataset.filter(filters).delete end |
#get_many_by_member(member) ⇒ Object
find all instances in this repo whose value for this property contains the given member instance
101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 101 def get_many_by_member(member) @repository.query do |dataset, property_columns| id_column = property_columns[@repository.identity_property].first dataset = dataset. join(@join_table, @qualified_left_key => id_column). filter(@qualified_right_key => member.id) dataset = dataset.filter(@filter) if @filter dataset = dataset.distinct if @distinct dataset end.to_a end |
#insert_join_table_rows(entity, id, values) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 115 def insert_join_table_rows(entity, id, values) rows = [] values.each_with_index do |value, index| value_id = value.id || if @auto_store_new target_repo.store_new(value); value.id else raise "value for ManyToMany mapped property #{@property_name} has no id, and :auto_store_new not specified" end row = {@left_key => id, @right_key => value_id} row[@order_column] = index if @order_column row.merge!(@filter) if @filter add_denormalized_columns_to_join_table_row(entity, value, row) rows << row end @join_table_dataset.multi_insert(rows) end |
#load_value(row, id, properties = nil) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 66 def load_value(row, id, properties=nil) target_repo.query(properties) do |dataset, property_columns| id_column = property_columns[target_repo.identity_property].first dataset = dataset. join(@join_table, @qualified_right_key => id_column). filter(@qualified_left_key => id) dataset = dataset.filter(@filter) if @filter dataset = dataset.distinct if @distinct @qualified_order_column ? dataset.order(@qualified_order_column) : dataset end.to_a end |
#load_values(rows, ids = nil, properties = nil, &b) ⇒ Object
efficient batch load for the non-lazy case
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 79 def load_values(rows, ids=nil, properties=nil, &b) query = target_repo.query(properties) do |dataset, mapping| id_column = mapping[target_repo.identity_property] dataset = dataset .join(@join_table, @qualified_right_key => id_column) .filter(@qualified_left_key => ids) .select(Sequel.as(@qualified_left_key,:_many_to_many_id)) dataset = dataset.filter(@filter) if @filter dataset = dataset.distinct if @distinct dataset = dataset.order(:_many_to_many_id, @qualified_order_column) if @qualified_order_column dataset end groups = []; id_to_group = {} ids.each_with_index {|id,index| id_to_group[id] = groups[index] = []} query.results_with_rows.each do |entity, row| id_to_group[row[:_many_to_many_id]] << entity end groups.each_with_index(&b) end |
#post_insert(entity, rows, insert_id) ⇒ Object
142 143 144 145 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 142 def post_insert(entity, rows, insert_id) return unless @writeable values = entity[@property_name] and insert_join_table_rows(entity, insert_id, values) end |
#post_update(entity, update_entity, rows, result_from_pre_update = nil) ⇒ Object
147 148 149 150 151 152 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 147 def post_update(entity, update_entity, rows, result_from_pre_update=nil) return unless @writeable update_values = update_entity[@property_name] or return delete_join_table_rows(entity.id) insert_join_table_rows(entity, entity.id, update_values) end |
#pre_delete(entity) ⇒ Object
154 155 156 |
# File 'lib/hold/sequel/property_mapper/many_to_many.rb', line 154 def pre_delete(entity) delete_join_table_rows(entity.id) if @manual_cascade_delete end |