lib/sequel/plugins/pg_array_associations.rb in sequel-4.9.0 vs lib/sequel/plugins/pg_array_associations.rb in sequel-4.10.0

- old
+ new

@@ -45,15 +45,13 @@ # # will save the changes to the album. # # They support some additional options specific to this plugin: # - # :array_type :: This allows you to specify the type of the array. This - # is only necessary to set in very narrow circumstances, - # such as when this plugin needs to create an array type, - # and typecasting is turned off or not setup correctly - # for the model object. + # :array_type :: This overrides the type of the array. By default, the type + # is determined by looking at the db_schema for the model, and if that fails, + # it defaults to :integer. # :raise_on_save_failure :: Do not raise exceptions for hook or validation failures when saving associated # objects in the add/remove methods (return nil instead). # :save_after_modify :: For pg_array_to_many associations, this makes the # the modification methods save the current object, # so they operate more similarly to the one_to_many @@ -75,10 +73,20 @@ module PgArrayAssociations # The AssociationReflection subclass for many_to_pg_array associations. class ManyToPgArrayAssociationReflection < Sequel::Model::Associations::AssociationReflection Sequel::Model::Associations::ASSOCIATION_TYPES[:many_to_pg_array] = self + def array_type + cached_fetch(:array_type) do + if (sch = associated_class.db_schema) && (s = sch[self[:key]]) && (t = s[:db_type]) + t + else + :integer + end + end + end + # The array column in the associated model containing foreign keys to # the current model. def associated_object_keys [self[:key]] end @@ -105,10 +113,15 @@ cached_fetch(:_eager_limit_strategy) do :ruby if self[:limit] end end + # Don't use a filter by associations limit strategy + def filter_by_associations_limit_strategy + nil + end + # Handle silent failure of add/remove methods if raise_on_save_failure is false. def handle_silent_modification_failure? self[:raise_on_save_failure] == false end @@ -129,10 +142,15 @@ false end private + # The predicate condition to use for the eager_loader. + def eager_loading_predicate_condition(keys) + Sequel.pg_array_op(predicate_key).overlaps(Sequel.pg_array(keys, array_type)) + end + def filter_by_associations_add_conditions_dataset_filter(ds) key = qualify(associated_class.table_name, self[:key]) ds.select{unnest(key)}.exclude(key=>nil) end @@ -147,16 +165,30 @@ end def reciprocal_type :pg_array_to_many end + + def use_placeholder_loader? + false + end end # The AssociationReflection subclass for pg_array_to_many associations. class PgArrayToManyAssociationReflection < Sequel::Model::Associations::AssociationReflection Sequel::Model::Associations::ASSOCIATION_TYPES[:pg_array_to_many] = self + def array_type + cached_fetch(:array_type) do + if (sch = self[:model].db_schema) && (s = sch[self[:key]]) && (t = s[:db_type]) + t + else + :integer + end + end + end + # An array containing the primary key for the associated model. def associated_object_keys Array(primary_key) end @@ -188,10 +220,15 @@ cached_fetch(:_eager_limit_strategy) do :ruby if self[:limit] end end + # Don't use a filter by associations limit strategy + def filter_by_associations_limit_strategy + nil + end + # Handle silent failure of add/remove methods if raise_on_save_failure is false # and save_after_modify is true. def handle_silent_modification_failure? self[:raise_on_save_failure] == false && self[:save_after_modify] end @@ -234,10 +271,14 @@ end def reciprocal_type :many_to_pg_array end + + def use_placeholder_loader? + false + end end module ClassMethods # Create a many_to_pg_array association, for the case where the associated # table contains the array with foreign keys pointing to the current table. @@ -262,32 +303,26 @@ pk = opts[:eager_loader_key] = opts[:primary_key] ||= model.primary_key opts[:key] = opts.default_key unless opts.has_key?(:key) key = opts[:key] key_column = opts[:key_column] ||= opts[:key] opts[:after_load].unshift(:array_uniq!) if opts[:uniq] - slice_range = opts.slice_range opts[:dataset] ||= lambda do - opts.associated_dataset.where(Sequel.pg_array_op(opts.predicate_key).contains([send(pk)])) + opts.associated_dataset.where(Sequel.pg_array_op(opts.predicate_key).contains(Sequel.pg_array([send(pk)], opts.array_type))) end opts[:eager_loader] ||= proc do |eo| id_map = eo[:id_map] - rows = eo[:rows] - opts.initialize_association_cache(rows) - klass = opts.associated_class - ds = model.eager_loading_dataset(opts, klass.where(Sequel.pg_array_op(opts.predicate_key).overlaps(id_map.keys)), nil, eo[:associations], eo) - ds.all do |assoc_record| + eager_load_results(opts, eo.merge(:loader=>false)) do |assoc_record| if pks ||= assoc_record.send(key) pks.each do |pkv| next unless objects = id_map[pkv] objects.each do |object| object.associations[name].push(assoc_record) end end end end - opts.apply_ruby_eager_limit_strategy(rows) end join_type = opts[:graph_join_type] select = opts[:graph_select] opts[:cartesian_product_number] ||= 1 @@ -314,82 +349,67 @@ ds = eo[:self] ds = ds.graph(eager_graph_dataset(opts, eo), conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block) ds end - def_association_dataset_methods(opts) + return if opts[:read_only] - unless opts[:read_only] - save_opts = {:validate=>opts[:validate]} - save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false + save_opts = {:validate=>opts[:validate]} + save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false - array_type = opts[:array_type] ||= :integer - adder = opts[:adder] || proc do |o| - if array = o.send(key) - array << send(pk) - else - o.send("#{key}=", Sequel.pg_array([send(pk)], array_type)) - end + opts[:adder] ||= proc do |o| + if array = o.send(key) + array << send(pk) + else + o.send("#{key}=", Sequel.pg_array([send(pk)], opts.array_type)) + end + o.save(save_opts) + end + + opts[:remover] ||= proc do |o| + if (array = o.send(key)) && !array.empty? + array.delete(send(pk)) o.save(save_opts) end - association_module_private_def(opts._add_method, opts, &adder) - - remover = opts[:remover] || proc do |o| - if (array = o.send(key)) && !array.empty? - array.delete(send(pk)) - o.save(save_opts) - end - end - association_module_private_def(opts._remove_method, opts, &remover) + end - clearer = opts[:clearer] || proc do - opts.associated_dataset.where(Sequel.pg_array_op(key).contains([send(pk)])).update(key=>Sequel.function(:array_remove, key, send(pk))) - end - association_module_private_def(opts._remove_all_method, opts, &clearer) - - def_add_method(opts) - def_remove_methods(opts) + opts[:clearer] ||= proc do + opts.associated_dataset.where(Sequel.pg_array_op(key).contains([send(pk)])).update(key=>Sequel.function(:array_remove, key, send(pk))) end end # Setup the pg_array_to_many-specific datasets, eager loaders, and modification methods. def def_pg_array_to_many(opts) name = opts[:name] - model = self opts[:key] = opts.default_key unless opts.has_key?(:key) key = opts[:key] key_column = opts[:key_column] ||= key opts[:eager_loader_key] = nil opts[:after_load].unshift(:array_uniq!) if opts[:uniq] - slice_range = opts.slice_range opts[:dataset] ||= lambda do opts.associated_dataset.where(opts.predicate_key=>send(key).to_a) end opts[:eager_loader] ||= proc do |eo| rows = eo[:rows] id_map = {} pkm = opts.primary_key_method - opts.initialize_association_cache(rows) rows.each do |object| if associated_pks = object.send(key) associated_pks.each do |apk| (id_map[apk] ||= []) << object end end end - klass = opts.associated_class - ds = model.eager_loading_dataset(opts, klass.where(opts.predicate_key=>id_map.keys), nil, eo[:associations], eo) - ds.all do |assoc_record| + eager_load_results(opts, eo.merge(:id_map=>id_map)) do |assoc_record| if objects = id_map[assoc_record.send(pkm)] objects.each do |object| object.associations[name].push(assoc_record) end end end - opts.apply_ruby_eager_limit_strategy(rows) end join_type = opts[:graph_join_type] select = opts[:graph_select] opts[:cartesian_product_number] ||= 1 @@ -416,55 +436,46 @@ ds = eo[:self] ds = ds.graph(eager_graph_dataset(opts, eo), conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block) ds end - def_association_dataset_methods(opts) + return if opts[:read_only] - unless opts[:read_only] - save_opts = {:validate=>opts[:validate]} - save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false - array_type = opts[:array_type] ||= :integer + save_opts = {:validate=>opts[:validate]} + save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false - if opts[:save_after_modify] - save_after_modify = proc do |obj| - obj.save(save_opts) - end + if opts[:save_after_modify] + save_after_modify = proc do |obj| + obj.save(save_opts) end + end - adder = opts[:adder] || proc do |o| - opk = o.send(opts.primary_key) - if array = send(key) - modified!(key) - array << opk - else - send("#{key}=", Sequel.pg_array([opk], array_type)) - end + opts[:adder] ||= proc do |o| + opk = o.send(opts.primary_key) + if array = send(key) + modified!(key) + array << opk + else + send("#{key}=", Sequel.pg_array([opk], opts.array_type)) + end + save_after_modify.call(self) if save_after_modify + end + + opts[:remover] ||= proc do |o| + if (array = send(key)) && !array.empty? + modified!(key) + array.delete(o.send(opts.primary_key)) save_after_modify.call(self) if save_after_modify end - association_module_private_def(opts._add_method, opts, &adder) - - remover = opts[:remover] || proc do |o| - if (array = send(key)) && !array.empty? - modified!(key) - array.delete(o.send(opts.primary_key)) - save_after_modify.call(self) if save_after_modify - end - end - association_module_private_def(opts._remove_method, opts, &remover) + end - clearer = opts[:clearer] || proc do - if (array = send(key)) && !array.empty? - modified!(key) - array.clear - save_after_modify.call(self) if save_after_modify - end + opts[:clearer] ||= proc do + if (array = send(key)) && !array.empty? + modified!(key) + array.clear + save_after_modify.call(self) if save_after_modify end - association_module_private_def(opts._remove_all_method, opts, &clearer) - - def_add_method(opts) - def_remove_methods(opts) end end end module DatasetMethods @@ -495,14 +506,14 @@ def pg_array_to_many_association_filter_expression(op, ref, obj) key = ref.qualify(model.table_name, ref[:key_column]) expr = case obj when Sequel::Model if pkv = obj.send(ref.primary_key_method) - Sequel.pg_array_op(key).contains([pkv]) + Sequel.pg_array_op(key).contains(Sequel.pg_array([pkv], ref.array_type)) end when Array if (pkvs = obj.map{|o| o.send(ref.primary_key_method)}.compact) && !pkvs.empty? - Sequel.pg_array(key).overlaps(pkvs) + Sequel.pg_array(key).overlaps(Sequel.pg_array(pkvs, ref.array_type)) end when Sequel::Dataset Sequel.function(:coalesce, Sequel.pg_array_op(key).overlaps(obj.select{array_agg(ref.qualify(obj.model.table_name, ref.primary_key))}), Sequel::SQL::Constants::FALSE) end expr = Sequel::SQL::Constants::FALSE unless expr