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.
- data/History.txt +6 -8
- data/lib/composite_primary_keys.rb +53 -36
- data/lib/composite_primary_keys/associations/association.rb +23 -0
- data/lib/composite_primary_keys/associations/association_scope.rb +67 -0
- data/lib/composite_primary_keys/associations/has_and_belongs_to_many_association.rb +31 -121
- data/lib/composite_primary_keys/associations/has_many_association.rb +27 -66
- data/lib/composite_primary_keys/associations/join_dependency/join_association.rb +22 -0
- data/lib/composite_primary_keys/associations/join_dependency/join_part.rb +39 -0
- data/lib/composite_primary_keys/associations/preloader/association.rb +61 -0
- data/lib/composite_primary_keys/associations/preloader/belongs_to.rb +13 -0
- data/lib/composite_primary_keys/associations/preloader/has_and_belongs_to_many.rb +46 -0
- data/lib/composite_primary_keys/attribute_methods/dirty.rb +30 -0
- data/lib/composite_primary_keys/attribute_methods/read.rb +88 -0
- data/lib/composite_primary_keys/attribute_methods/write.rb +33 -0
- data/lib/composite_primary_keys/base.rb +18 -70
- data/lib/composite_primary_keys/composite_predicates.rb +53 -0
- data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +6 -4
- data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +19 -41
- data/lib/composite_primary_keys/fixtures.rb +19 -6
- data/lib/composite_primary_keys/persistence.rb +32 -13
- data/lib/composite_primary_keys/relation.rb +23 -16
- data/lib/composite_primary_keys/relation/calculations.rb +48 -0
- data/lib/composite_primary_keys/relation/finder_methods.rb +117 -0
- data/lib/composite_primary_keys/relation/query_methods.rb +24 -0
- data/lib/composite_primary_keys/validations/uniqueness.rb +19 -23
- data/lib/composite_primary_keys/version.rb +5 -5
- data/test/connections/native_mysql/connection.rb +1 -1
- data/test/fixtures/articles.yml +1 -0
- data/test/fixtures/products.yml +2 -4
- data/test/fixtures/readings.yml +1 -0
- data/test/fixtures/suburbs.yml +1 -4
- data/test/fixtures/users.yml +1 -0
- data/test/test_associations.rb +61 -63
- data/test/test_attributes.rb +16 -21
- data/test/test_create.rb +3 -3
- data/test/test_delete.rb +87 -84
- data/test/{test_clone.rb → test_dup.rb} +8 -5
- data/test/test_exists.rb +22 -10
- data/test/test_habtm.rb +0 -74
- data/test/test_ids.rb +2 -1
- data/test/test_miscellaneous.rb +2 -2
- data/test/test_polymorphic.rb +1 -1
- data/test/test_suite.rb +1 -1
- data/test/test_update.rb +3 -3
- metadata +76 -75
- data/lib/composite_primary_keys/association_preload.rb +0 -158
- data/lib/composite_primary_keys/associations.rb +0 -155
- data/lib/composite_primary_keys/associations/association_proxy.rb +0 -33
- data/lib/composite_primary_keys/associations/has_one_association.rb +0 -27
- data/lib/composite_primary_keys/associations/through_association_scope.rb +0 -103
- data/lib/composite_primary_keys/attribute_methods.rb +0 -84
- data/lib/composite_primary_keys/calculations.rb +0 -31
- data/lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb +0 -21
- data/lib/composite_primary_keys/connection_adapters/oracle_adapter.rb +0 -15
- data/lib/composite_primary_keys/connection_adapters/oracle_enhanced_adapter.rb +0 -17
- data/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb +0 -15
- data/lib/composite_primary_keys/finder_methods.rb +0 -123
- data/lib/composite_primary_keys/primary_key.rb +0 -19
- data/lib/composite_primary_keys/query_methods.rb +0 -24
- data/lib/composite_primary_keys/read.rb +0 -25
- data/lib/composite_primary_keys/reflection.rb +0 -37
- 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
|