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
@@ -1,84 +0,0 @@
1
- module CompositePrimaryKeys
2
- module ActiveRecord
3
- module AttributeMethods #:nodoc:
4
- def self.append_features(base)
5
- super
6
- base.send(:extend, ClassMethods)
7
- end
8
-
9
- module ClassMethods
10
- # Define an attribute reader method. Cope with nil column.
11
- def define_read_method(symbol, attr_name, column)
12
- cast_code = column.type_cast_code('v') if column
13
- cast_code = "::#{cast_code}" if cast_code && cast_code.match('ActiveRecord::.*')
14
- access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
15
-
16
- unless self.primary_keys.include?(attr_name.to_sym)
17
- access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
18
- end
19
-
20
- if cache_attribute?(attr_name)
21
- access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
22
- end
23
-
24
- evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end"
25
- end
26
-
27
- # Evaluate the definition for an attribute related method
28
- def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name)
29
- unless primary_keys.include?(method_name.to_sym)
30
- generated_methods << method_name
31
- end
32
-
33
- begin
34
- class_eval(method_definition, __FILE__, __LINE__)
35
- rescue SyntaxError => err
36
- generated_methods.delete(attr_name)
37
- if logger
38
- logger.warn "Exception occurred during reader method compilation."
39
- logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
40
- logger.warn "#{err.message}"
41
- end
42
- end
43
- end
44
- end
45
-
46
- # Allows access to the object attributes, which are held in the @attributes hash, as though they
47
- # were first-class methods. So a Person class with a name attribute can use Person#name and
48
- # Person#name= and never directly use the attributes hash -- except for multiple assigns with
49
- # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
50
- # the completed attribute is not nil or 0.
51
- #
52
- # It's also possible to instantiate related objects, so a Client class belonging to the clients
53
- # table with a master_id foreign key can instantiate master through Client#master.
54
- def method_missing(method_id, *args, &block)
55
- method_name = method_id.to_s
56
-
57
- # If we haven't generated any methods yet, generate them, then
58
- # see if we've created the method we're looking for.
59
- if !self.class.generated_methods?
60
- self.class.define_attribute_methods
61
-
62
- if self.class.generated_methods.include?(method_name)
63
- return self.send(method_id, *args, &block)
64
- end
65
- end
66
-
67
- if self.class.primary_keys.include?(method_name.to_sym)
68
- ids[self.class.primary_keys.index(method_name.to_sym)]
69
- elsif md = self.class.match_attribute_method?(method_name)
70
- attribute_name, method_type = md.pre_match, md.to_s
71
- if @attributes.include?(attribute_name)
72
- __send__("attribute#{method_type}", attribute_name, *args, &block)
73
- else
74
- super
75
- end
76
- elsif @attributes.include?(method_name)
77
- read_attribute(method_name)
78
- else
79
- super
80
- end
81
- end
82
- end
83
- end
84
- end
@@ -1,31 +0,0 @@
1
- module ActiveRecord
2
- module Calculations
3
- alias :execute_simple_calculation_ar :execute_simple_calculation
4
- def execute_simple_calculation(operation, column_name, distinct)
5
- # CPK
6
- if column_name.kind_of?(Array)
7
- execute_simple_calculation_cpk(operation, column_name, distinct)
8
- else
9
- execute_simple_calculation_ar(operation, column_name, distinct)
10
- end
11
- end
12
-
13
- def execute_simple_calculation_cpk(operation, column_name, distinct)
14
- projection = self.primary_keys.map do |key|
15
- attribute = arel_table[key]
16
- self.arel.visitor.accept(attribute)
17
- end.join(', ')
18
-
19
- # relation = self.clone
20
- # relation.select_values = ["DISTINCT #{projection}"]
21
-
22
- manager = Arel::SelectManager.new(arel_engine)
23
- manager.project("DISTINCT #{projection}")
24
- manager.from(arel_table)
25
-
26
- query = Arel::Table.new('dummy').project('count(*)')
27
- query = query.from("(#{manager.to_sql}) AS subquery")
28
- type_cast_calculated_value(@klass.connection.select_value(query.to_sql), column_for(column_name), operation)
29
- end
30
- end
31
- end
@@ -1,21 +0,0 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- class IBM_DBAdapter < AbstractAdapter
4
-
5
- # This mightn't be in Core, but count(distinct x,y) doesn't work for me
6
- def supports_count_distinct? #:nodoc:
7
- false
8
- end
9
-
10
- alias_method :quote_original, :quote
11
- def quote(value, column = nil)
12
- if value.kind_of?(String) && column && [:integer, :float].include?(column.type)
13
- value = column.type == :integer ? value.to_i : value.to_f
14
- value.to_s
15
- else
16
- quote_original(value, column)
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,15 +0,0 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- class OracleAdapter < AbstractAdapter
4
-
5
- # This mightn't be in Core, but count(distinct x,y) doesn't work for me
6
- def supports_count_distinct? #:nodoc:
7
- false
8
- end
9
-
10
- def concat(*columns)
11
- "(#{columns.join('||')})"
12
- end
13
- end
14
- end
15
- end
@@ -1,17 +0,0 @@
1
- # Added to OracleEnhancedAdapter version 1.1.4
2
- #
3
- # module ActiveRecord
4
- # module ConnectionAdapters
5
- # class OracleEnhancedAdapter < AbstractAdapter
6
- #
7
- # # This mightn't be in Core, but count(distinct x,y) doesn't work for me
8
- # def supports_count_distinct? #:nodoc:
9
- # false
10
- # end
11
- #
12
- # def concat(*columns)
13
- # "(#{columns.join('||')})"
14
- # end
15
- # end
16
- # end
17
- # end
@@ -1,15 +0,0 @@
1
- require 'active_record/connection_adapters/sqlite3_adapter'
2
-
3
- module ActiveRecord
4
- module ConnectionAdapters #:nodoc:
5
- class SQLite3Adapter # :nodoc:
6
- def supports_count_distinct? #:nodoc:
7
- false
8
- end
9
-
10
- def concat(*columns)
11
- "(#{columns.join('||')})"
12
- end
13
- end
14
- end
15
- end
@@ -1,123 +0,0 @@
1
- module CompositePrimaryKeys
2
- module ActiveRecord
3
- module FinderMethods
4
- module InstanceMethods
5
- def construct_limited_ids_condition(relation)
6
- orders = relation.order_values.join(", ")
7
-
8
- # CPK
9
- # values = @klass.connection.distinct("#{@klass.connection.quote_table_name @klass.table_name}.#{@klass.primary_key}", orders)
10
- keys = @klass.primary_keys.map do |key|
11
- "#{@klass.connection.quote_table_name @klass.table_name}.#{key}"
12
- end
13
-
14
- values = @klass.connection.distinct(keys.join(', '), orders)
15
-
16
- ids_array = relation.select(values).collect {|row| row[@klass.primary_key]}
17
-
18
- # CPK
19
- # ids_array.empty? ? raise(ThrowResult) : primary_key.in(ids_array)
20
-
21
- # OR together each and expression (key=value and key=value) that matches an id set
22
- # since we only need to match 0 or more records
23
- or_expressions = ids_array.map do |id_set|
24
- # AND together "key=value" exprssios to match each id set
25
- and_expressions = [self.primary_keys, id_set].transpose.map do |key, id|
26
- table[key].eq(id)
27
- end
28
-
29
- # Merge all the ands together
30
- first = and_expressions.shift
31
- Arel::Nodes::Grouping.new(and_expressions.inject(first) do |memo, expr|
32
- Arel::Nodes::And.new(memo, expr)
33
- end)
34
- end
35
-
36
- first = or_expressions.shift
37
- Arel::Nodes::Grouping.new(or_expressions.inject(first) do |memo, expr|
38
- Arel::Nodes::Or.new(memo, expr)
39
- end)
40
- end
41
-
42
- def exists?(id = nil)
43
- # ID can be:
44
- # Array - ['department_id = ? and location_id = ?', 1, 1]
45
- # Array -> [1,2]
46
- # CompositeKeys -> [1,2]
47
-
48
- id = id.id if ::ActiveRecord::Base === id
49
-
50
- case id
51
- # CPK
52
- when CompositePrimaryKeys::CompositeKeys
53
- relation = select("1").limit(1)
54
- relation = relation.where_cpk_id(id) if id
55
- relation.first ? true : false
56
- when Array
57
- # CPK
58
- if id.first.is_a?(String) and id.first.match(/\?/)
59
- where(id).exists?
60
- else
61
- exists?(id.to_composite_keys)
62
- end
63
- when Hash
64
- where(id).exists?
65
- else
66
- # CPK
67
- #relation = select(primary_key).limit(1)
68
- #relation = relation.where(primary_key.eq(id)) if id
69
- relation = select("1").limit(1)
70
- relation = relation.where_cpk_id(id) if id
71
- relation.first ? true : false
72
- end
73
- end
74
-
75
- def find_with_ids(*ids, &block)
76
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
77
-
78
- # Supports:
79
- # find('1,2') -> ['1,2']
80
- # find(1,2) -> [1,2]
81
- # find([1,2]) -> [['1,2']]
82
- # find([1,2], [3,4]) -> [[1,2],[3,4]]
83
- #
84
- # Does *not* support:
85
- # find('1,2', '3,4') -> ['1,2','3,4']
86
-
87
- # Normalize incoming data. Note the last arg can be nil. Happens
88
- # when find is called with nil options like the reload method does.
89
- ids.compact!
90
- ids = [ids] unless ids.first.kind_of?(Array)
91
-
92
- results = ids.map do |cpk_ids|
93
- cpk_ids = if cpk_ids.length == 1
94
- cpk_ids.first.split(CompositePrimaryKeys::ID_SEP).to_composite_keys
95
- else
96
- cpk_ids.to_composite_keys
97
- end
98
-
99
- unless cpk_ids.length == @klass.primary_keys.length
100
- raise "#{cpk_ids.inspect}: Incorrect number of primary keys for #{@klass.name}: #{@klass.primary_keys.inspect}"
101
- end
102
-
103
- new_relation = clone
104
- [@klass.primary_keys, cpk_ids].transpose.map do |key, id|
105
- new_relation = new_relation.where(key => id)
106
- end
107
-
108
- records = new_relation.to_a
109
-
110
- if records.empty?
111
- conditions = new_relation.arel.where_sql
112
- raise(::ActiveRecord::RecordNotFound,
113
- "Couldn't find #{@klass.name} with ID=#{cpk_ids} #{conditions}")
114
- end
115
- records
116
- end.flatten
117
-
118
- ids.length == 1 ? results.first : results
119
- end
120
- end
121
- end
122
- end
123
- end
@@ -1,19 +0,0 @@
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
@@ -1,24 +0,0 @@
1
- module ActiveRecord
2
- module QueryMethods
3
- def reverse_order
4
- order_clause = arel.order_clauses.join(', ')
5
- relation = except(:order)
6
-
7
- # CPK
8
- # order = order_clause.blank? ?
9
- # "#{@klass.table_name}.#{@klass.primary_key} DESC" :
10
- # reverse_sql_order(order_clause)
11
-
12
- order = unless order_clause.blank?
13
- reverse_sql_order(order_clause)
14
- else
15
- primary_keys = composite? ? @klass.primary_keys : [@klass.primary_key]
16
- primary_keys.map do |key|
17
- "#{@klass.table_name}.#{key} DESC"
18
- end.join(", ")
19
- end
20
-
21
- relation.order Arel::SqlLiteral.new order
22
- end
23
- end
24
- end
@@ -1,25 +0,0 @@
1
- module ActiveRecord
2
- module AttributeMethods
3
- module Read
4
- def read_attribute(attr_name)
5
- attr_name = attr_name.to_s
6
- # CPK
7
- # attr_name = self.class.primary_key if attr_name == 'id'
8
- attr_name = self.class.primary_key if (attr_name == 'id' and !self.composite?)
9
- if !(value = @attributes[attr_name]).nil?
10
- if column = column_for_attribute(attr_name)
11
- if unserializable_attribute?(attr_name, column)
12
- unserialize_attribute(attr_name)
13
- else
14
- column.type_cast(value)
15
- end
16
- else
17
- value
18
- end
19
- else
20
- nil
21
- end
22
- end
23
- end
24
- end
25
- end
@@ -1,37 +0,0 @@
1
- module ActiveRecord
2
- module Reflection
3
- class AssociationReflection
4
- def derive_primary_key
5
- if options[:foreign_key]
6
- options[:foreign_key]
7
- elsif belongs_to?
8
- "#{name}_id"
9
- elsif options[:as]
10
- "#{options[:as]}_id"
11
- else
12
- active_record.name.foreign_key
13
- end
14
- end
15
-
16
- def cpk_primary_key
17
- # Make sure the returned key(s) are an array
18
- @cpk_primary_key ||= [derive_primary_key].flatten
19
- end
20
-
21
- def primary_key_name
22
- @primary_key_name ||= derive_primary_key_name
23
- end
24
-
25
- def derive_primary_key_name
26
- result = derive_primary_key
27
-
28
- # CPK
29
- if result.is_a?(Array)
30
- result.to_composite_keys.to_s
31
- else
32
- result
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,18 +0,0 @@
1
- module ActiveRecord
2
- module AttributeMethods
3
- module Write
4
- def write_attribute(attr_name, value)
5
- attr_name = attr_name.to_s
6
- # CPK
7
- # attr_name = self.class.primary_key if attr_name == 'id'
8
- attr_name = self.class.primary_key if (attr_name == 'id' and !self.composite?)
9
- @attributes_cache.delete(attr_name)
10
- if (column = column_for_attribute(attr_name)) && column.number?
11
- @attributes[attr_name] = convert_number_column_value(value)
12
- else
13
- @attributes[attr_name] = value
14
- end
15
- end
16
- end
17
- end
18
- end