composite_primary_keys 3.1.7 → 3.1.8

Sign up to get free protection for your applications and to get access to all the features.
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