class ActiveRecord::Relation
Public Class Methods
new(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
click to toggle source
# File lib/composite_primary_keys/relation.rb, line 4 def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {}) initialize_without_cpk(klass, table: table, predicate_builder: predicate_builder, values: values) add_cpk_support if klass && klass.composite? end
Also aliased as: initialize_without_cpk
Public Instance Methods
add_cpk_support()
click to toggle source
# File lib/composite_primary_keys/relation.rb, line 15 def add_cpk_support extend CompositePrimaryKeys::CompositeRelation end
cpk_exists_subquery(stmt)
click to toggle source
CPK. This is an alternative to IN subqueries. It is used by sqlserver. Example query:
UPDATE reference_codes SET … WHERE EXISTS
(SELECT 1 FROM reference_codes cpk_child WHERE reference_codes.reference_type_id = cpk_child.reference_type_id AND reference_codes.reference_code = cpk_child.reference_code)
# File lib/composite_primary_keys/relation.rb, line 145 def cpk_exists_subquery(stmt) arel_attributes = primary_keys.map do |key| table[key] end.to_composite_keys # Clone the query subselect = arel.clone # Alias the table - we assume just one table aliased_table = subselect.froms.first aliased_table.table_alias = "cpk_child" # Project - really we could just set this to "1" subselect.projections = arel_attributes # Setup correlation to the outer query via where clauses primary_keys.map do |key| outer_attribute = arel_table[key] inner_attribute = aliased_table[key] where = outer_attribute.eq(inner_attribute) subselect.where(where) end stmt.wheres = [Arel::Nodes::Exists.new(subselect)] end
cpk_in_subquery(stmt)
click to toggle source
Used by postgresql, sqlite, mariadb and oracle. Example query:
UPDATE reference_codes SET … WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
(SELECT reference_codes.reference_type_id, reference_codes.reference_code FROM reference_codes)
# File lib/composite_primary_keys/relation.rb, line 121 def cpk_in_subquery(stmt) # Setup the subquery subquery = arel.clone subquery.projections = primary_keys.map do |key| arel_table[key] end where_fields = primary_keys.map do |key| arel_table[key] end where = Arel::Nodes::Grouping.new(where_fields).in(subquery) stmt.wheres = [where] end
cpk_mysql_subquery(stmt)
click to toggle source
CPK. This is the old way CPK created subqueries and is used by MySql. MySQL does not support referencing the same table that is being UPDATEd or DELETEd in a subquery so we obfuscate it. The ugly query looks like this:
UPDATE ‘reference_codes` SET … WHERE (reference_codes.reference_type_id, reference_codes.reference_code) IN
(SELECT reference_type_id,reference_code FROM (SELECT DISTINCT `reference_codes`.`reference_type_id`, `reference_codes`.`reference_code` FROM `reference_codes`) __active_record_temp)
# File lib/composite_primary_keys/relation.rb, line 180 def cpk_mysql_subquery(stmt) arel_attributes = primary_keys.map do |key| table[key] end.to_composite_keys subselect = arel.clone subselect.projections = arel_attributes # Materialize subquery by adding distinct # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' subselect.distinct unless arel.limit || arel.offset || arel.orders.any? key_name = arel_attributes.map(&:name).join(',') manager = Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name)) stmt.wheres = [Arel::Nodes::In.new(arel_attributes, manager.ast)] end
cpk_subquery(stmt)
click to toggle source
CPK
# File lib/composite_primary_keys/relation.rb, line 96 def cpk_subquery(stmt) # For update and delete statements we need a way to specify which records should # get updated. By default, Rails creates a nested IN subquery that uses the primary # key. Postgresql, Sqlite, MariaDb and Oracle support IN subqueries with multiple # columns but MySQL and SqlServer do not. Instead SQL server supports EXISTS queries # and MySQL supports obfuscated IN queries. Thus we need to check the type of # database adapter to decide how to proceed. if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) cpk_mysql_subquery(stmt) elsif defined?(ActiveRecord::ConnectionAdapters::TrilogyAdapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::TrilogyAdapter) cpk_mysql_subquery(stmt) elsif defined?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) && connection.is_a?(ActiveRecord::ConnectionAdapters::SQLServerAdapter) cpk_exists_subquery(stmt) else cpk_in_subquery(stmt) end end
delete_all()
click to toggle source
# File lib/composite_primary_keys/relation.rb, line 58 def delete_all invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method| value = @values[method] method == :distinct ? value : value&.any? end if invalid_methods.any? raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}") end if eager_loading? relation = apply_join_dependency return relation.delete_all end stmt = Arel::DeleteManager.new stmt.from(arel.join_sources.empty? ? table : arel.source) stmt.key = table[primary_key] # CPK if @klass.composite? stmt = Arel::DeleteManager.new stmt.from(arel_table) cpk_subquery(stmt) else stmt.wheres = arel.constraints end stmt.take(arel.limit) stmt.offset(arel.offset) stmt.order(*arel.orders) affected = @klass.connection.delete(stmt, "#{@klass} Destroy") reset affected end
initialize_copy(other)
click to toggle source
# File lib/composite_primary_keys/relation.rb, line 10 def initialize_copy(other) initialize_copy_without_cpk(other) add_cpk_support if klass.composite? end
Also aliased as: initialize_copy_without_cpk
initialize_without_cpk(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
Alias for: new
update_all(updates)
click to toggle source
# File lib/composite_primary_keys/relation.rb, line 19 def update_all(updates) raise ArgumentError, "Empty list of attributes to change" if updates.blank? if eager_loading? relation = apply_join_dependency return relation.update_all(updates) end stmt = Arel::UpdateManager.new stmt.table(arel.join_sources.empty? ? table : arel.source) stmt.key = table[primary_key] # CPK if @klass.composite? stmt = Arel::UpdateManager.new stmt.table(arel_table) cpk_subquery(stmt) else stmt.wheres = arel.constraints end stmt.take(arel.limit) stmt.offset(arel.offset) stmt.order(*arel.orders) if updates.is_a?(Hash) if klass.locking_enabled? && !updates.key?(klass.locking_column) && !updates.key?(klass.locking_column.to_sym) attr = table[klass.locking_column] updates[attr.name] = _increment_attribute(attr) end stmt.set _substitute_values(updates) else stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name)) end @klass.connection.update stmt, "#{@klass} Update All" end