Class: ActiveRecord::Relation
- Inherits:
-
Object
- Object
- ActiveRecord::Relation
- Defined in:
- lib/composite_primary_keys/relation.rb,
lib/composite_primary_keys/relation/where_clause.rb
Defined Under Namespace
Classes: WhereClause
Instance Method Summary collapse
- #add_cpk_support ⇒ Object
-
#cpk_exists_subquery(stmt) ⇒ Object
CPK.
-
#cpk_in_subquery(stmt) ⇒ Object
Used by postgresql, sqlite, mariadb and oracle.
-
#cpk_mysql_subquery(stmt) ⇒ Object
CPK.
-
#cpk_subquery(stmt) ⇒ Object
CPK.
- #delete_all ⇒ Object
-
#initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {}) ⇒ Relation
constructor
A new instance of Relation.
- #initialize_copy(other) ⇒ Object
- #initialize_copy_without_cpk ⇒ Object
- #initialize_without_cpk ⇒ Object
- #update_all(updates) ⇒ Object
Constructor Details
#initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {}) ⇒ Relation
Returns a new instance of Relation.
4 5 6 7 |
# 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 |
Instance Method Details
#add_cpk_support ⇒ Object
15 16 17 |
# File 'lib/composite_primary_keys/relation.rb', line 15 def add_cpk_support extend CompositePrimaryKeys::CompositeRelation end |
#cpk_exists_subquery(stmt) ⇒ Object
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)
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# 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) ⇒ Object
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)
121 122 123 124 125 126 127 128 129 130 131 132 133 |
# 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) ⇒ Object
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)
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# 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) ⇒ Object
CPK
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# 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 ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# 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) ⇒ Object
10 11 12 13 |
# 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 |
#initialize_copy_without_cpk ⇒ Object
9 |
# File 'lib/composite_primary_keys/relation.rb', line 9 alias :initialize_copy_without_cpk :initialize_copy |
#initialize_without_cpk ⇒ Object
3 |
# File 'lib/composite_primary_keys/relation.rb', line 3 alias :initialize_without_cpk :initialize |
#update_all(updates) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# 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 |