composite_primary_keys 3.1.11 → 4.0.0.beta1

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.
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