composite_primary_keys 3.1.7 → 3.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt CHANGED
@@ -1,10 +1,11 @@
1
- == 3.1.7 2011-05-19
1
+ == 3.1.8 2011-05-26
2
+ * Fix calling clear on a HABTM associate (David Rueck)
3
+
4
+ == 3.1.7 2011-05-26
2
5
  * Support regular AR models having one or many composite models (Jacques Fuentes)
3
6
  * Minor test cleanup
4
7
  * Make version requirements more explicit
5
8
  * Remove Arel extensions used for calculations
6
- * Fix test that included wrong error constant
7
-
8
9
 
9
10
  == 3.1.6 2011-04-03
10
11
  * Updated belongs_to association to be a bit more flexible with non-CPK
@@ -13,14 +14,12 @@
13
14
  * Fix write issue when one of they keys in a composite key is
14
15
  called id (Tom Hughes)
15
16
 
16
-
17
17
  == 3.1.5 2011-03-24
18
18
  * Fix simple calculation methods
19
19
  * Fix instantiation of cpk records via associations.
20
20
  * Fix Relation#delete
21
21
  * Fix Relation#destroy
22
22
 
23
-
24
23
  == 3.1.4 2011-03-06
25
24
  * Support ActiveRecord 3.0.5 (interpolate_sql was removed and
26
25
  replaced by interpolate_and_sanitize_sql)
@@ -25,7 +25,7 @@ $:.unshift(File.dirname(__FILE__)) unless
25
25
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
26
26
 
27
27
  unless defined?(ActiveRecord)
28
- gem 'arel', '~> 2.0.0'
28
+ require 'rubygems'
29
29
  gem 'activerecord', '>= 3.0.5', '~> 3.0.0'
30
30
  require 'active_record'
31
31
  end
@@ -0,0 +1,23 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Association
4
+ def creation_attributes
5
+ attributes = {}
6
+
7
+ if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
8
+ # CPK
9
+ # attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
10
+ Array(reflection.foreign_key).zip(Array(reflection.active_record_primary_key)).each do |key1, key2|
11
+ attributes[key1] = owner[key2]
12
+ end
13
+
14
+ if reflection.options[:as]
15
+ attributes[reflection.type] = owner.class.base_class.name
16
+ end
17
+ end
18
+
19
+ attributes
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,67 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class AssociationScope
4
+ def add_constraints(scope)
5
+ tables = construct_tables
6
+
7
+ chain.each_with_index do |reflection, i|
8
+ table, foreign_table = tables.shift, tables.first
9
+
10
+ if reflection.source_macro == :has_and_belongs_to_many
11
+ join_table = tables.shift
12
+
13
+ # CPK
14
+ # scope = scope.joins(join(
15
+ # join_table,
16
+ # table[reflection.active_record_primary_key].
17
+ # eq(join_table[reflection.association_foreign_key])
18
+ #))
19
+ predicate = cpk_join_predicate(table, reflection.association_primary_key,
20
+ join_table, reflection.association_foreign_key)
21
+ scope = scope.joins(join(join_table, predicate))
22
+
23
+ table, foreign_table = join_table, tables.first
24
+ end
25
+
26
+ if reflection.source_macro == :belongs_to
27
+ key = reflection.association_primary_key
28
+ foreign_key = reflection.foreign_key
29
+ else
30
+ key = reflection.foreign_key
31
+ foreign_key = reflection.active_record_primary_key
32
+ end
33
+
34
+ if reflection == chain.last
35
+ # CPK
36
+ # scope = scope.where(table[key].eq(owner[foreign_key]))
37
+ predicate = cpk_join_predicate(table, key, owner, foreign_key)
38
+ scope = scope.where(predicate)
39
+
40
+ conditions[i].each do |condition|
41
+ if options[:through] && condition.is_a?(Hash)
42
+ condition = { table.name => condition }
43
+ end
44
+
45
+ scope = scope.where(interpolate(condition))
46
+ end
47
+ else
48
+ # CPK
49
+ # constraint = table[key].eq(foreign_table[foreign_key])
50
+ constraint = cpk_join_predicate(table, key, foreign_table, foreign_key)
51
+ scope = scope.where(predicate)
52
+
53
+ join = join(foreign_table, constraint)
54
+
55
+ scope = scope.joins(join)
56
+
57
+ unless conditions[i].empty?
58
+ scope = scope.where(sanitize(conditions[i], table))
59
+ end
60
+ end
61
+ end
62
+
63
+ scope
64
+ end
65
+ end
66
+ end
67
+ end
@@ -88,6 +88,56 @@ module ActiveRecord
88
88
 
89
89
  return true
90
90
  end
91
- end
91
+
92
+ # CPK
93
+ #def delete_records(records)
94
+ # if sql = @reflection.options[:delete_sql]
95
+ # records.each { |record| @owner.connection.delete(interpolate_and_sanitize_sql(sql, record)) }
96
+ # else
97
+ # relation = Arel::Table.new(@reflection.options[:join_table])
98
+ # relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
99
+ # and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
100
+ # ).delete
101
+ # end
102
+ #end
103
+
104
+ # CPK
105
+ def delete_records(records)
106
+ if sql = @reflection.options[:delete_sql]
107
+ records.each { |record| @owner.connection.delete(interpolate_and_sanitize_sql(sql, record)) }
108
+ else
109
+ relation = Arel::Table.new(@reflection.options[:join_table])
110
+
111
+ if @reflection.cpk_primary_key
112
+ owner_conditions = []
113
+ @reflection.cpk_primary_key.each_with_index do |column,i|
114
+ owner_conditions << relation[column.to_sym].eq(@owner.id[i])
115
+ end
116
+ owner_conditions_arel = owner_conditions.inject { |conds, cond| conds.and(cond) }
117
+ else
118
+ owner_conditions_arel = relation[@reflection.primary_key_name].eq(@owner.id)
119
+ end
120
+
121
+ if @reflection.association_foreign_key.kind_of?(Array)
122
+ association_conditions = []
123
+ records.each do |rec|
124
+ record_conditions = []
125
+ @reflection.association_foreign_key.each_with_index do |column,i|
126
+ record_conditions << relation[column.to_sym].eq(rec.id[i])
127
+ end
128
+ association_conditions << record_conditions.inject { |conds, cond| conds.and(cond) }
129
+ end
130
+ association_conditions_arel = association_conditions.inject { |conds, cond| conds.or(cond) }
131
+ else
132
+ association_conditions_arel = relation[@reflection.association_foreign_key].in(records.map { |x| x.id }.compact)
133
+ end
134
+
135
+ all_conditions_arel = owner_conditions_arel.and(association_conditions_arel)
136
+
137
+ relation.where(all_conditions_arel).delete
138
+ end
139
+ end
140
+
141
+ end
92
142
  end
93
- end
143
+ end
@@ -0,0 +1,22 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency
4
+ class JoinAssociation
5
+ def build_constraint(reflection, table, key, foreign_table, foreign_key)
6
+ # CPK
7
+ # constraint = table[key].eq(foreign_table[foreign_key])
8
+ constraint = cpk_join_predicate(table, key, foreign_table, foreign_key)
9
+
10
+ if reflection.klass.finder_needs_type_condition?
11
+ constraint = table.create_and([
12
+ constraint,
13
+ reflection.klass.send(:type_condition, table)
14
+ ])
15
+ end
16
+
17
+ constraint
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class JoinDependency
4
+ class JoinPart
5
+ def aliased_primary_key
6
+ # CPK
7
+ # "#{aliased_prefix}_r0"
8
+
9
+ active_record.composite? ?
10
+ primary_key.inject([]) {|aliased_keys, key| aliased_keys << "#{ aliased_prefix }_r#{aliased_keys.length}"} :
11
+ "#{ aliased_prefix }_r0"
12
+ end
13
+
14
+ def record_id(row)
15
+ # CPK
16
+ # row[aliased_primary_key]
17
+ active_record.composite? ?
18
+ aliased_primary_key.map {|key| row[key]}.to_composite_keys :
19
+ row[aliased_primary_key]
20
+ end
21
+
22
+ def column_names_with_alias
23
+ unless @column_names_with_alias
24
+ @column_names_with_alias = []
25
+
26
+ # CPK
27
+ #([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
28
+ keys = active_record.composite? ? primary_key.map(&:to_s) : [primary_key]
29
+
30
+ (keys + (column_names - keys)).each_with_index do |column_name, i|
31
+ @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
32
+ end
33
+ end
34
+ @column_names_with_alias
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,61 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class Association
5
+ def records_for(ids)
6
+ # CPK
7
+ # scoped.where(association_key.in(ids))
8
+ predicate = cpk_in_predicate(table, reflection.foreign_key, ids)
9
+ scoped.where(predicate)
10
+ end
11
+
12
+ def associated_records_by_owner
13
+ # CPK
14
+ # owner_keys = owners.map { |owner| owner[owner_key_name] }.compact.uniq
15
+ owner_keys = owners.map do |owner|
16
+ Array(owner_key_name).map do |owner_key|
17
+ owner[owner_key]
18
+ end
19
+ end.compact.uniq
20
+
21
+ if klass.nil? || owner_keys.empty?
22
+ records = []
23
+ else
24
+ # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
25
+ # Make several smaller queries if necessary or make one query if the adapter supports it
26
+ sliced = owner_keys.each_slice(model.connection.in_clause_length || owner_keys.size)
27
+ records = sliced.map { |slice| records_for(slice) }.flatten
28
+ end
29
+
30
+ # Each record may have multiple owners, and vice-versa
31
+ records_by_owner = Hash[owners.map { |owner| [owner, []] }]
32
+ records.each do |record|
33
+ # CPK
34
+ # owner_key = record[association_key_name].to_s
35
+ owner_key = Array(association_key_name).map do |key_name|
36
+ record[key_name]
37
+ end.join(CompositePrimaryKeys::ID_SEP)
38
+
39
+ owners_by_key[owner_key].each do |owner|
40
+ records_by_owner[owner] << record
41
+ end
42
+ end
43
+ records_by_owner
44
+ end
45
+
46
+ def owners_by_key
47
+ @owners_by_key ||= owners.group_by do |owner|
48
+ # CPK
49
+ # key = owner[owner_key_name]
50
+ key = Array(owner_key_name).map do |key_name|
51
+ owner[key_name]
52
+ end
53
+ # CPK
54
+ # key && key.to_s
55
+ key && key.join(CompositePrimaryKeys::ID_SEP)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class BelongsTo
5
+ def records_for(ids)
6
+ # CPK
7
+ predicate = cpk_in_predicate(table, association_key_name, ids)
8
+ scoped.where(predicate)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasAndBelongsToMany
5
+ def records_for(ids)
6
+ # CPK
7
+ #scope = super
8
+ predicate = cpk_in_predicate(join_table, reflection.foreign_key, ids)
9
+ scope = scoped.where(predicate)
10
+
11
+ klass.connection.select_all(scope.arel.to_sql, 'SQL', scope.bind_values)
12
+ end
13
+
14
+ def join
15
+ # CPK
16
+ #condition = table[reflection.association_primary_key].eq(
17
+ # join_table[reflection.association_foreign_key])
18
+ condition = cpk_join_predicate(table, reflection.association_primary_key,
19
+ join_table, reflection.association_foreign_key)
20
+
21
+ table.create_join(join_table, table.create_on(condition))
22
+ end
23
+
24
+ def association_key_alias(field)
25
+ "ar_association_key_name_#{field.to_s}"
26
+ end
27
+
28
+ def join_select
29
+ # CPK
30
+ # association_key.as(Arel.sql(association_key_name))
31
+ Array(reflection.foreign_key).map do |key|
32
+ join_table[key].as(Arel.sql(association_key_alias(key)))
33
+ end
34
+ end
35
+
36
+ def association_key_name
37
+ # CPK
38
+ # 'ar_association_key_name'
39
+ Array(reflection.foreign_key).map do |key|
40
+ association_key_alias(key)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveRecord
2
+ module AttributeMethods #:nodoc:
3
+ module PrimaryKey
4
+ def to_key
5
+ # CPK
6
+ #key = send(self.class.primary_key)
7
+ #[key] if key
8
+
9
+ primary_key = self.class.primary_key
10
+ if primary_key.is_a?(Array)
11
+ primary_key.collect{|k| send(k)}
12
+ else
13
+ key = send(primary_key)
14
+ [key] if key
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,88 @@
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module AttributeMethods
4
+ module Read
5
+ def self.included(base)
6
+ base.send(:extend, ClassMethods)
7
+ alias [] read_attribute
8
+ end
9
+
10
+ module ClassMethods
11
+ def define_read_method(method_name, attr_name, column)
12
+ cast_code = column.type_cast_code('v')
13
+ # CPK - this is a really horrid hack, needed to get
14
+ # right class namespace :(
15
+ if cast_code.match(/^ActiveRecord/)
16
+ cast_code = "::#{cast_code}"
17
+ end
18
+
19
+ access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
20
+
21
+ # CPK
22
+ # unless attr_name.to_s == self.primary_key.to_s
23
+ # access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
24
+ # end
25
+ unless self.primary_keys.include?(attr_name.to_sym)
26
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
27
+ end
28
+
29
+ if cache_attribute?(attr_name)
30
+ access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
31
+ end
32
+
33
+ # Where possible, generate the method by evalling a string, as this will result in
34
+ # faster accesses because it avoids the block eval and then string eval incurred
35
+ # by the second branch.
36
+ #
37
+ # The second, slower, branch is necessary to support instances where the database
38
+ # returns columns with extra stuff in (like 'my_column(omg)').
39
+ if method_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
40
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
41
+ def _#{method_name}
42
+ #{access_code}
43
+ end
44
+
45
+ alias #{method_name} _#{method_name}
46
+ STR
47
+ else
48
+ generated_attribute_methods.module_eval do
49
+ define_method("_#{method_name}") { eval(access_code) }
50
+ alias_method(method_name, "_#{method_name}")
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ def read_attribute(attr_name)
57
+ # CPK
58
+ if attr_name.kind_of?(Array)
59
+ attr_name.map {|name| read_attribute(name)}.to_composite_keys
60
+ elsif respond_to? "_#{attr_name}"
61
+ send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s)
62
+ else
63
+ _read_attribute attr_name
64
+ end
65
+ end
66
+
67
+ def _read_attribute(attr_name)
68
+ attr_name = attr_name.to_s
69
+ # CPK
70
+ # attr_name = self.class.primary_key if attr_name == 'id'
71
+ attr_name = self.class.primary_key if (attr_name == 'id' and !self.composite?)
72
+ value = @attributes[attr_name]
73
+ unless value.nil?
74
+ if column = column_for_attribute(attr_name)
75
+ if unserializable_attribute?(attr_name, column)
76
+ unserialize_attribute(attr_name)
77
+ else
78
+ column.type_cast(value)
79
+ end
80
+ else
81
+ value
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,40 @@
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module AttributeMethods
4
+ module Write
5
+ def self.included(base)
6
+ alias []= write_attribute
7
+ alias_method :raw_write_attribute, :write_attribute
8
+ end
9
+
10
+ def write_attribute(attr_name, value)
11
+ if attr_name.kind_of?(Array)
12
+ unless value.length == attr_name.length
13
+ raise "Number of attr_names and values do not match"
14
+ end
15
+ [attr_name, value].transpose.map {|name,val| _write_attribute(name, val)}
16
+ value
17
+ else
18
+ _write_attribute(attr_name, value)
19
+ end
20
+ end
21
+
22
+ def _write_attribute(attr_name, value)
23
+ attr_name = attr_name.to_s
24
+ # CPK
25
+ # attr_name = self.class.primary_key if attr_name == 'id'
26
+ attr_name = self.class.primary_key if (attr_name == 'id' and !self.composite?)
27
+ @attributes_cache.delete(attr_name)
28
+ if (column = column_for_attribute(attr_name)) && column.number?
29
+ @attributes[attr_name] = convert_number_column_value(value)
30
+ else
31
+ @attributes[attr_name] = value
32
+ end
33
+ end
34
+
35
+
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,44 @@
1
+ module CompositePrimaryKeys
2
+ module Predicates
3
+ def cpk_or_predicate(predicates)
4
+ result = predicates.shift
5
+ predicates.each do |predicate|
6
+ result = result.or(predicate)
7
+ end
8
+ result
9
+ end
10
+
11
+ def cpk_id_predicate(table, keys, values)
12
+ eq_predicates = keys.zip(values).map do |key, value|
13
+ table[key].eq(value)
14
+ end
15
+ Arel::Nodes::And.new(eq_predicates)
16
+ end
17
+
18
+ def cpk_join_predicate(table1, key1, table2, key2)
19
+ key1_fields = Array(key1).map {|key| table1[key]}
20
+ key2_fields = Array(key2).map {|key| table2[key]}
21
+
22
+ predicates = key1_fields.zip(key2_fields).map do |key_field1, key_field2|
23
+ key_field1.eq(key_field2)
24
+ end
25
+ Arel::Nodes::And.new(predicates)
26
+ end
27
+
28
+ def cpk_in_predicate(table, primary_keys, ids)
29
+ and_predicates = ids.map do |id_set|
30
+ eq_predicates = Array(primary_keys).zip(id_set).map do |primary_key, value|
31
+ table[primary_key].eq(value)
32
+ end
33
+ Arel::Nodes::And.new(eq_predicates)
34
+ end
35
+
36
+ cpk_or_predicate(and_predicates)
37
+ end
38
+ end
39
+ end
40
+
41
+ ActiveRecord::Associations::AssociationScope.send(:include, CompositePrimaryKeys::Predicates)
42
+ ActiveRecord::Associations::JoinDependency::JoinAssociation.send(:include, CompositePrimaryKeys::Predicates)
43
+ ActiveRecord::Associations::Preloader::Association.send(:include, CompositePrimaryKeys::Predicates)
44
+ ActiveRecord::Relation.send(:include, CompositePrimaryKeys::Predicates)
@@ -0,0 +1,29 @@
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module NamedScope
4
+ module ClassMethods
5
+ def scoped(options = nil)
6
+ result = if options
7
+ scoped.apply_finder_options(options)
8
+ else
9
+ if current_scope
10
+ current_scope.clone
11
+ else
12
+ scope = relation.clone
13
+ scope.default_scoped = true
14
+ scope
15
+ end
16
+ end
17
+
18
+ # CPK
19
+ class << result
20
+ include CompositePrimaryKeys::ActiveRecord::FinderMethods
21
+ include CompositePrimaryKeys::ActiveRecord::QueryMethods
22
+ include CompositePrimaryKeys::ActiveRecord::Relation
23
+ end
24
+ result
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,119 @@
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module FinderMethods
4
+ def construct_limited_ids_condition(relation)
5
+ orders = relation.order_values
6
+ # CPK
7
+ # values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
8
+ keys = @klass.primary_keys.map do |key|
9
+ "#{@klass.connection.quote_table_name @klass.table_name}.#{key}"
10
+ end
11
+ values = @klass.connection.distinct(keys.join(', '), orders)
12
+
13
+ relation = relation.dup
14
+
15
+ ids_array = relation.select(values).collect {|row| row[primary_key]}
16
+ # CPK
17
+ # ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
18
+
19
+ # OR together each and expression (key=value and key=value) that matches an id set
20
+ # since we only need to match 0 or more records
21
+ or_expressions = ids_array.map do |id_set|
22
+ # AND together "key=value" exprssios to match each id set
23
+ and_expressions = [self.primary_keys, id_set].transpose.map do |key, id|
24
+ table[key].eq(id)
25
+ end
26
+ Arel::Nodes::And.new(and_expressions)
27
+ end
28
+
29
+ first = or_expressions.shift
30
+ Arel::Nodes::Grouping.new(or_expressions.inject(first) do |memo, expr|
31
+ Arel::Nodes::Or.new(memo, expr)
32
+ end)
33
+ end
34
+
35
+ def exists?(id = nil)
36
+ # ID can be:
37
+ # Array - ['department_id = ? and location_id = ?', 1, 1]
38
+ # Array -> [1,2]
39
+ # CompositeKeys -> [1,2]
40
+
41
+ id = id.id if ::ActiveRecord::Base === id
42
+
43
+ join_dependency = construct_join_dependency_for_association_find
44
+ relation = construct_relation_for_association_find(join_dependency)
45
+ relation = relation.except(:select).select("1").limit(1)
46
+
47
+ # CPK
48
+ #case id
49
+ #when Array, Hash
50
+ # relation = relation.where(id)
51
+ #else
52
+ # relation = relation.where(table[primary_key].eq(id)) if id
53
+ #end
54
+
55
+ case id
56
+ when CompositePrimaryKeys::CompositeKeys
57
+ relation = relation.where(cpk_id_predicate(table, primary_key, id))
58
+ when Array
59
+ if !id.first.kind_of?(String)
60
+ return self.exists?(id.to_composite_keys)
61
+ else
62
+ relation = relation.where(id)
63
+ end
64
+ when Hash
65
+ relation = relation.where(id)
66
+ else
67
+ raise(ArgumentError, "Unsupported id sent to exists?")
68
+ end
69
+ connection.select_value(relation.to_sql) ? true : false
70
+ end
71
+
72
+ def find_with_ids(*ids, &block)
73
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
74
+
75
+ # Supports:
76
+ # find('1,2') -> ['1,2']
77
+ # find(1,2) -> [1,2]
78
+ # find([1,2]) -> [['1,2']]
79
+ # find([1,2], [3,4]) -> [[1,2],[3,4]]
80
+ #
81
+ # Does *not* support:
82
+ # find('1,2', '3,4') -> ['1,2','3,4']
83
+
84
+ # Normalize incoming data. Note the last arg can be nil. Happens
85
+ # when find is called with nil options like the reload method does.
86
+ ids.compact!
87
+ ids = [ids] unless ids.first.kind_of?(Array)
88
+
89
+ results = ids.map do |cpk_ids|
90
+ cpk_ids = if cpk_ids.length == 1
91
+ cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
92
+ else
93
+ cpk_ids.to_composite_keys
94
+ end
95
+
96
+ unless cpk_ids.length == @klass.primary_keys.length
97
+ raise "#{cpk_ids.inspect}: Incorrect number of primary keys for #{@klass.name}: #{@klass.primary_keys.inspect}"
98
+ end
99
+
100
+ new_relation = clone
101
+ [@klass.primary_keys, cpk_ids].transpose.map do |key, id|
102
+ new_relation = new_relation.where(key => id)
103
+ end
104
+
105
+ records = new_relation.to_a
106
+
107
+ if records.empty?
108
+ conditions = new_relation.arel.where_sql
109
+ raise(::ActiveRecord::RecordNotFound,
110
+ "Couldn't find #{@klass.name} with ID=#{cpk_ids} #{conditions}")
111
+ end
112
+ records
113
+ end.flatten
114
+
115
+ ids.length == 1 ? results.first : results
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,24 @@
1
+ module CompositePrimaryKeys
2
+ module ActiveRecord
3
+ module QueryMethods
4
+ def reverse_order
5
+ order_clause = arel.order_clauses
6
+
7
+ # CPK
8
+ # order = order_clause.empty? ?
9
+ # "#{table_name}.#{primary_key} DESC" :
10
+ # reverse_sql_order(order_clause).join(', ')
11
+
12
+ order = unless order_clause.empty?
13
+ reverse_sql_order(order_clause).join(', ')
14
+ else
15
+ klass.primary_key.map do |key|
16
+ "#{table_name}.#{key} DESC"
17
+ end.join(", ")
18
+ end
19
+
20
+ except(:order).order(Arel.sql(order))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -2,7 +2,7 @@ module CompositePrimaryKeys
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 3
4
4
  MINOR = 1
5
- TINY = 7
5
+ TINY = 8
6
6
  STRING = [MAJOR, MINOR, TINY].join('.')
7
7
  end
8
8
  end
@@ -1,21 +1,20 @@
1
1
  dir = File.dirname(__FILE__)
2
2
  PROJECT_ROOT = File.expand_path(File.join(dir, '..'))
3
3
 
4
- adapter = ENV["ADAPTER"] || 'postgresql'
4
+ adapter = ENV["ADAPTER"] || "postgresql"
5
5
 
6
- require 'pp'
7
- require 'test/unit'
8
- require 'hash_tricks'
9
- require 'rubygems'
6
+ require "pp"
7
+ require "test/unit"
8
+ require "hash_tricks"
10
9
 
11
10
  # To make debugging easier, test within this source tree versus an installed gem
12
11
  #require 'composite_primary_keys'
13
12
  require File.join(PROJECT_ROOT, "lib", "composite_primary_keys")
14
13
 
15
- require 'active_record/fixtures'
16
- require File.join(PROJECT_ROOT, 'test', 'connections', 'connection_spec')
14
+ require File.join(PROJECT_ROOT, "test", "connections", "connection_spec")
17
15
  require File.join(PROJECT_ROOT, "test", "connections", "native_#{adapter}", "connection")
18
16
 
17
+
19
18
  # Tell ActiveRecord where to find models
20
19
  ActiveSupport::Dependencies.autoload_paths << File.join(PROJECT_ROOT, 'test', 'fixtures')
21
20
 
@@ -7,7 +7,7 @@ def connection_string
7
7
  options['u'] = SPEC['username'] if SPEC['username']
8
8
  options['p'] = SPEC['password'] if SPEC['password']
9
9
  options['S'] = SPEC['sock'] if SPEC['sock']
10
- options.map { |key, value| "-#{key}#{value}" }.join(" ")
10
+ options.map { |key, value| "-#{key} #{value}" }.join(" ")
11
11
  end
12
12
 
13
13
  # Adapter config setup in locals/database_connections.rb
@@ -160,7 +160,15 @@ class TestAssociations < ActiveSupport::TestCase
160
160
  assert_equal 2, @restaurant.suburbs.size
161
161
  end
162
162
 
163
- def test_has_one_with_primary_key
163
+ def test_habtm_clear
164
+ @restaurant = Restaurant.find([1,1])
165
+ assert_equal 2, @restaurant.suburbs.size
166
+ @restaurant.suburbs.clear
167
+ @restaurant = Restaurant.find([1,1])
168
+ assert_equal 0, @restaurant.suburbs.size
169
+ end
170
+
171
+ def test_has_many_with_primary_key
164
172
  @membership = Membership.find([1, 1])
165
173
  assert_equal 2, @membership.reading.id
166
174
  end
@@ -235,4 +243,4 @@ class TestAssociations < ActiveSupport::TestCase
235
243
  assert_equal(1, memberships.length)
236
244
  assert_equal([1,1], memberships[0].id)
237
245
  end
238
- end
246
+ end
data/test/test_create.rb CHANGED
@@ -38,7 +38,7 @@ class TestCreate < ActiveSupport::TestCase
38
38
  begin
39
39
  @obj = @klass.create(@klass_info[:create].block(@klass.primary_key))
40
40
  @successful = !composite?
41
- rescue ActiveRecord::CompositeKeyError
41
+ rescue CompositePrimaryKeys::ActiveRecord::CompositeKeyError
42
42
  @successful = false
43
43
  rescue
44
44
  flunk "Incorrect exception raised: #{$!}, #{$!.class}"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: composite_primary_keys
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 3
8
8
  - 1
9
- - 7
10
- version: 3.1.7
9
+ - 8
10
+ version: 3.1.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - Dr Nic Williams
@@ -16,30 +16,22 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-05-19 00:00:00 Z
19
+ date: 2011-05-26 00:00:00 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
- name: arel
22
+ name: activerecord
23
23
  prerelease: false
24
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- hash: 15
29
+ hash: 7
30
30
  segments:
31
- - 2
31
+ - 3
32
32
  - 0
33
33
  - 0
34
- version: 2.0.0
35
- type: :runtime
36
- version_requirements: *id001
37
- - !ruby/object:Gem::Dependency
38
- name: activerecord
39
- prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
34
+ version: 3.0.0
43
35
  - - ">="
44
36
  - !ruby/object:Gem::Version
45
37
  hash: 13
@@ -48,20 +40,12 @@ dependencies:
48
40
  - 0
49
41
  - 5
50
42
  version: 3.0.5
51
- - - ~>
52
- - !ruby/object:Gem::Version
53
- hash: 7
54
- segments:
55
- - 3
56
- - 0
57
- - 0
58
- version: 3.0.0
59
43
  type: :runtime
60
- version_requirements: *id002
44
+ version_requirements: *id001
61
45
  - !ruby/object:Gem::Dependency
62
46
  name: rspec
63
47
  prerelease: false
64
- requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirement: &id002 !ruby/object:Gem::Requirement
65
49
  none: false
66
50
  requirements:
67
51
  - - ">="
@@ -71,7 +55,7 @@ dependencies:
71
55
  - 0
72
56
  version: "0"
73
57
  type: :development
74
- version_requirements: *id003
58
+ version_requirements: *id002
75
59
  description: Composite key support for ActiveRecord 3
76
60
  email:
77
61
  - drnicwilliams@gmail.com
@@ -89,17 +73,28 @@ files:
89
73
  - init.rb
90
74
  - install.rb
91
75
  - loader.rb
76
+ - lib/composite_primary_keys/associations/association.rb
92
77
  - lib/composite_primary_keys/associations/association_proxy.rb
78
+ - lib/composite_primary_keys/associations/association_scope.rb
93
79
  - lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb
94
80
  - lib/composite_primary_keys/associations/has_many_association.rb
95
81
  - lib/composite_primary_keys/associations/has_one_association.rb
82
+ - lib/composite_primary_keys/associations/join_dependency/join_association.rb
83
+ - lib/composite_primary_keys/associations/join_dependency/join_part.rb
84
+ - lib/composite_primary_keys/associations/preloader/association.rb
85
+ - lib/composite_primary_keys/associations/preloader/belongs_to.rb
86
+ - lib/composite_primary_keys/associations/preloader/has_and_belongs_to_many.rb
96
87
  - lib/composite_primary_keys/associations/through_association_scope.rb
97
88
  - lib/composite_primary_keys/associations.rb
98
89
  - lib/composite_primary_keys/association_preload.rb
90
+ - lib/composite_primary_keys/attribute_methods/primary_key.rb
91
+ - lib/composite_primary_keys/attribute_methods/read.rb
92
+ - lib/composite_primary_keys/attribute_methods/write.rb
99
93
  - lib/composite_primary_keys/attribute_methods.rb
100
94
  - lib/composite_primary_keys/base.rb
101
95
  - lib/composite_primary_keys/calculations.rb
102
96
  - lib/composite_primary_keys/composite_arrays.rb
97
+ - lib/composite_primary_keys/composite_predicates.rb
103
98
  - lib/composite_primary_keys/connection_adapters/abstract_adapter.rb
104
99
  - lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb
105
100
  - lib/composite_primary_keys/connection_adapters/oracle_adapter.rb
@@ -108,11 +103,14 @@ files:
108
103
  - lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb
109
104
  - lib/composite_primary_keys/finder_methods.rb
110
105
  - lib/composite_primary_keys/fixtures.rb
106
+ - lib/composite_primary_keys/named_scope.rb
111
107
  - lib/composite_primary_keys/persistence.rb
112
108
  - lib/composite_primary_keys/primary_key.rb
113
109
  - lib/composite_primary_keys/query_methods.rb
114
110
  - lib/composite_primary_keys/read.rb
115
111
  - lib/composite_primary_keys/reflection.rb
112
+ - lib/composite_primary_keys/relation/finder_methods.rb
113
+ - lib/composite_primary_keys/relation/query_methods.rb
116
114
  - lib/composite_primary_keys/relation.rb
117
115
  - lib/composite_primary_keys/validations/uniqueness.rb
118
116
  - lib/composite_primary_keys/version.rb