composite_primary_keys 3.1.11 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/History.txt +6 -8
  2. data/lib/composite_primary_keys.rb +53 -36
  3. data/lib/composite_primary_keys/associations/association.rb +23 -0
  4. data/lib/composite_primary_keys/associations/association_scope.rb +67 -0
  5. data/lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb +31 -121
  6. data/lib/composite_primary_keys/associations/has_many_association.rb +27 -66
  7. data/lib/composite_primary_keys/associations/join_dependency/join_association.rb +22 -0
  8. data/lib/composite_primary_keys/associations/join_dependency/join_part.rb +39 -0
  9. data/lib/composite_primary_keys/associations/preloader/association.rb +61 -0
  10. data/lib/composite_primary_keys/associations/preloader/belongs_to.rb +13 -0
  11. data/lib/composite_primary_keys/associations/preloader/has_and_belongs_to_many.rb +46 -0
  12. data/lib/composite_primary_keys/attribute_methods/dirty.rb +30 -0
  13. data/lib/composite_primary_keys/attribute_methods/read.rb +88 -0
  14. data/lib/composite_primary_keys/attribute_methods/write.rb +33 -0
  15. data/lib/composite_primary_keys/base.rb +18 -70
  16. data/lib/composite_primary_keys/composite_predicates.rb +53 -0
  17. data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +6 -4
  18. data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +19 -41
  19. data/lib/composite_primary_keys/fixtures.rb +19 -6
  20. data/lib/composite_primary_keys/persistence.rb +32 -13
  21. data/lib/composite_primary_keys/relation.rb +23 -16
  22. data/lib/composite_primary_keys/relation/calculations.rb +48 -0
  23. data/lib/composite_primary_keys/relation/finder_methods.rb +117 -0
  24. data/lib/composite_primary_keys/relation/query_methods.rb +24 -0
  25. data/lib/composite_primary_keys/validations/uniqueness.rb +19 -23
  26. data/lib/composite_primary_keys/version.rb +5 -5
  27. data/test/connections/native_mysql/connection.rb +1 -1
  28. data/test/fixtures/articles.yml +1 -0
  29. data/test/fixtures/products.yml +2 -4
  30. data/test/fixtures/readings.yml +1 -0
  31. data/test/fixtures/suburbs.yml +1 -4
  32. data/test/fixtures/users.yml +1 -0
  33. data/test/test_associations.rb +61 -63
  34. data/test/test_attributes.rb +16 -21
  35. data/test/test_create.rb +3 -3
  36. data/test/test_delete.rb +87 -84
  37. data/test/{test_clone.rb → test_dup.rb} +8 -5
  38. data/test/test_exists.rb +22 -10
  39. data/test/test_habtm.rb +0 -74
  40. data/test/test_ids.rb +2 -1
  41. data/test/test_miscellaneous.rb +2 -2
  42. data/test/test_polymorphic.rb +1 -1
  43. data/test/test_suite.rb +1 -1
  44. data/test/test_update.rb +3 -3
  45. metadata +76 -75
  46. data/lib/composite_primary_keys/association_preload.rb +0 -158
  47. data/lib/composite_primary_keys/associations.rb +0 -155
  48. data/lib/composite_primary_keys/associations/association_proxy.rb +0 -33
  49. data/lib/composite_primary_keys/associations/has_one_association.rb +0 -27
  50. data/lib/composite_primary_keys/associations/through_association_scope.rb +0 -103
  51. data/lib/composite_primary_keys/attribute_methods.rb +0 -84
  52. data/lib/composite_primary_keys/calculations.rb +0 -31
  53. data/lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb +0 -21
  54. data/lib/composite_primary_keys/connection_adapters/oracle_adapter.rb +0 -15
  55. data/lib/composite_primary_keys/connection_adapters/oracle_enhanced_adapter.rb +0 -17
  56. data/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb +0 -15
  57. data/lib/composite_primary_keys/finder_methods.rb +0 -123
  58. data/lib/composite_primary_keys/primary_key.rb +0 -19
  59. data/lib/composite_primary_keys/query_methods.rb +0 -24
  60. data/lib/composite_primary_keys/read.rb +0 -25
  61. data/lib/composite_primary_keys/reflection.rb +0 -37
  62. data/lib/composite_primary_keys/write.rb +0 -18
@@ -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,30 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Dirty
4
+ def write_attribute(attr, value)
5
+ # CPK
6
+ # attr = attr.to_s
7
+ attr = attr.to_s unless self.composite?
8
+
9
+ # The attribute already has an unsaved change.
10
+ if attribute_changed?(attr)
11
+ old = @changed_attributes[attr]
12
+ @changed_attributes.delete(attr) unless field_changed?(attr, old, value)
13
+ else
14
+ old = clone_attribute_value(:read_attribute, attr)
15
+ # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
16
+ old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
17
+ @changed_attributes[attr] = old if field_changed?(attr, old, value)
18
+ end
19
+
20
+ # Carry on.
21
+ super(attr, value)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ ActiveRecord::Base.class_eval do
28
+ alias :[]= :write_attribute
29
+ public :[]=
30
+ end
@@ -0,0 +1,88 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Read
4
+ module ClassMethods
5
+ def define_read_method(method_name, attr_name, column)
6
+ cast_code = column.type_cast_code('v')
7
+ # CPK - this is a really horrid hack, needed to get
8
+ # right class namespace :(
9
+ if cast_code.match(/^ActiveRecord/)
10
+ cast_code = "::#{cast_code}"
11
+ end
12
+
13
+ access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
14
+
15
+ # CPK
16
+ # unless attr_name.to_s == self.primary_key.to_s
17
+ # access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
18
+ # end
19
+ primary_keys = Array(self.primary_key)
20
+
21
+ unless primary_keys.include?(attr_name.to_s)
22
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
23
+ end
24
+
25
+ if cache_attribute?(attr_name)
26
+ access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
27
+ end
28
+
29
+ # Where possible, generate the method by evalling a string, as this will result in
30
+ # faster accesses because it avoids the block eval and then string eval incurred
31
+ # by the second branch.
32
+ #
33
+ # The second, slower, branch is necessary to support instances where the database
34
+ # returns columns with extra stuff in (like 'my_column(omg)').
35
+ if method_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
36
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
37
+ def _#{method_name}
38
+ #{access_code}
39
+ end
40
+
41
+ alias #{method_name} _#{method_name}
42
+ STR
43
+ else
44
+ generated_attribute_methods.module_eval do
45
+ define_method("_#{method_name}") { eval(access_code) }
46
+ alias_method(method_name, "_#{method_name}")
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def read_attribute(attr_name)
53
+ # CPK
54
+ if attr_name.kind_of?(Array)
55
+ attr_name.map {|name| read_attribute(name)}.to_composite_keys
56
+ elsif respond_to? "_#{attr_name}"
57
+ send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s)
58
+ else
59
+ _read_attribute attr_name
60
+ end
61
+ end
62
+
63
+
64
+ def _read_attribute(attr_name)
65
+ attr_name = attr_name.to_s
66
+ # CPK
67
+ # attr_name = self.class.primary_key if attr_name == 'id'
68
+ attr_name = self.class.primary_key if (attr_name == 'id' and !self.composite?)
69
+ value = @attributes[attr_name]
70
+ unless value.nil?
71
+ if column = column_for_attribute(attr_name)
72
+ if unserializable_attribute?(attr_name, column)
73
+ unserialize_attribute(attr_name)
74
+ else
75
+ column.type_cast(value)
76
+ end
77
+ else
78
+ value
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ ActiveRecord::Base.class_eval do
87
+ alias :[] :read_attribute
88
+ end
@@ -0,0 +1,33 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Write
4
+ def write_attribute(attr_name, value)
5
+ # CPK
6
+ if attr_name.kind_of?(Array)
7
+ unless value.length == attr_name.length
8
+ raise "Number of attr_names and values do not match"
9
+ end
10
+ [attr_name, value].transpose.map {|name,val| write_attribute(name, val)}
11
+ value
12
+ else
13
+ attr_name = attr_name.to_s
14
+ # CPK
15
+ # attr_name = self.class.primary_key if attr_name == 'id'
16
+ attr_name = self.class.primary_key if (attr_name == 'id' and !self.composite?)
17
+ @attributes_cache.delete(attr_name)
18
+ if (column = column_for_attribute(attr_name)) && column.number?
19
+ @attributes[attr_name] = convert_number_column_value(value)
20
+ else
21
+ @attributes[attr_name] = value
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ #ActiveRecord::Base.class_eval do
30
+ # alias :[]= :write_attribute
31
+ # alias :raw_write_attribute :write_attribute
32
+ # public :[]=
33
+ #end
@@ -16,12 +16,11 @@ module ActiveRecord
16
16
  end
17
17
 
18
18
  cattr_accessor :primary_keys
19
- self.primary_keys = keys.map { |k| k.to_sym }
19
+ self.primary_keys = keys.map { |k| k.to_sym }.to_composite_keys
20
20
 
21
21
  class_eval <<-EOV
22
- extend CompositeClassMethods
22
+ extend CompositeClassMethods
23
23
  include CompositeInstanceMethods
24
- include CompositePrimaryKeys::ActiveRecord::AssociationPreload
25
24
  EOV
26
25
  end
27
26
 
@@ -34,38 +33,6 @@ module ActiveRecord
34
33
  self.class.composite?
35
34
  end
36
35
 
37
- def [](attr_name)
38
- # CPK
39
- if attr_name.is_a?(String) and attr_name != attr_name.split(CompositePrimaryKeys::ID_SEP).first
40
- attr_name = attr_name.split(CompositePrimaryKeys::ID_SEP)
41
- end
42
-
43
- # CPK
44
- if attr_name.is_a?(Array)
45
- values = attr_name.map {|name| read_attribute(name)}
46
- CompositePrimaryKeys::CompositeKeys.new(values)
47
- else
48
- read_attribute(attr_name)
49
- end
50
- end
51
-
52
- def []=(attr_name, value)
53
- # CPK
54
- if attr_name.is_a?(String) and attr_name != attr_name.split(CompositePrimaryKeys::ID_SEP).first
55
- attr_name = attr_name.split(CompositePrimaryKeys::ID_SEP)
56
- end
57
-
58
- if attr_name.is_a? Array
59
- unless value.length == attr_name.length
60
- raise "Number of attr_names and values do not match"
61
- end
62
- [attr_name, value].transpose.map {|name,val| write_attribute(name, val)}
63
- value
64
- else
65
- write_attribute(attr_name, value)
66
- end
67
- end
68
-
69
36
  module CompositeClassMethods
70
37
  def primary_key
71
38
  primary_keys
@@ -84,20 +51,6 @@ module ActiveRecord
84
51
  def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
85
52
  many_ids.map {|ids| "#{left_bracket}#{CompositePrimaryKeys::CompositeKeys.new(ids)}#{right_bracket}"}.join(list_sep)
86
53
  end
87
-
88
- def relation #:nodoc:
89
- @relation ||= begin
90
- result = Relation.new(self, arel_table)
91
- # CPK
92
- class << result
93
- include CompositePrimaryKeys::ActiveRecord::FinderMethods::InstanceMethods
94
- include CompositePrimaryKeys::ActiveRecord::Relation::InstanceMethods
95
- end
96
- result
97
- end
98
-
99
- finder_needs_type_condition? ? @relation.where(type_condition) : @relation
100
- end
101
54
  end
102
55
 
103
56
  module CompositeInstanceMethods
@@ -143,35 +96,30 @@ module ActiveRecord
143
96
  ids.is_a?(Array) ? super(comparison_object) && ids.all? {|id| id.present?} : super(comparison_object)
144
97
  end
145
98
 
146
- # Cloned objects have no id assigned and are treated as new records. Note that this is a "shallow" clone
147
- # as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
148
- # application specific and is therefore left to the application to implement according to its need.
149
- def initialize_copy(other)
150
- # Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
151
- # deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
152
- # over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
153
- # For example in the test suite the topic model's after_initialize method sets the author_email_address to
154
- # test@test.com. I would have thought this would mean that all cloned models would have an author email address
155
- # of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
156
- # after_initialize callback has to be run *before* the copying of the atrributes rather than afterwards in order
157
- # for all tests to pass. This makes no sense to me.
158
- callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
99
+ def initialize_dup(other)
159
100
  cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
160
101
  # CPK
161
102
  #cloned_attributes.delete(self.class.primary_key)
162
103
  self.class.primary_key.each {|key| cloned_attributes.delete(key.to_s)}
163
104
 
164
105
  @attributes = cloned_attributes
165
- clear_aggregation_cache
166
- @attributes_cache = {}
167
- @new_record = true
168
- ensure_proper_type
169
106
 
170
- if scope = self.class.send(:current_scoped_methods)
171
- create_with = scope.scope_for_create
172
- create_with.each { |att,value| self.send("#{att}=", value) } if create_with
107
+ _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
108
+
109
+ @changed_attributes = {}
110
+ attributes_from_column_definition.each do |attr, orig_value|
111
+ @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
173
112
  end
113
+
114
+ @aggregation_cache = {}
115
+ @association_cache = {}
116
+ @attributes_cache = {}
117
+ @new_record = true
118
+
119
+ ensure_proper_type
120
+ populate_with_current_scope_attributes
121
+ clear_timestamp_attributes
174
122
  end
175
123
  end
176
124
  end
177
- end
125
+ end