globe-composite_primary_keys 3.0.1
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 +203 -0
- data/Manifest.txt +121 -0
- data/README.txt +41 -0
- data/README_DB2.txt +33 -0
- data/Rakefile +30 -0
- data/composite_primary_keys.gemspec +17 -0
- data/lib/adapter_helper/base.rb +63 -0
- data/lib/adapter_helper/mysql.rb +13 -0
- data/lib/adapter_helper/oracle.rb +12 -0
- data/lib/adapter_helper/oracle_enhanced.rb +12 -0
- data/lib/adapter_helper/postgresql.rb +13 -0
- data/lib/adapter_helper/sqlite3.rb +13 -0
- data/lib/composite_primary_keys.rb +63 -0
- data/lib/composite_primary_keys/association_preload.rb +162 -0
- data/lib/composite_primary_keys/associations.rb +159 -0
- data/lib/composite_primary_keys/attribute_methods.rb +84 -0
- data/lib/composite_primary_keys/base.rb +200 -0
- data/lib/composite_primary_keys/composite_arrays.rb +29 -0
- data/lib/composite_primary_keys/connection_adapters/abstract_adapter.rb +9 -0
- data/lib/composite_primary_keys/connection_adapters/ibm_db_adapter.rb +21 -0
- data/lib/composite_primary_keys/connection_adapters/oracle_adapter.rb +15 -0
- data/lib/composite_primary_keys/connection_adapters/oracle_enhanced_adapter.rb +17 -0
- data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +53 -0
- data/lib/composite_primary_keys/connection_adapters/sqlite3_adapter.rb +15 -0
- data/lib/composite_primary_keys/finder_methods.rb +68 -0
- data/lib/composite_primary_keys/fixtures.rb +8 -0
- data/lib/composite_primary_keys/read.rb +25 -0
- data/lib/composite_primary_keys/reflection.rb +39 -0
- data/lib/composite_primary_keys/relation.rb +31 -0
- data/lib/composite_primary_keys/through_association_scope.rb +212 -0
- data/lib/composite_primary_keys/validations/uniqueness.rb +118 -0
- data/lib/composite_primary_keys/version.rb +9 -0
- data/loader.rb +24 -0
- data/local/database_connections.rb.sample +12 -0
- data/local/paths.rb.sample +2 -0
- data/local/tasks.rb.sample +2 -0
- data/scripts/console.rb +48 -0
- data/scripts/txt2html +67 -0
- data/scripts/txt2js +59 -0
- data/tasks/activerecord_selection.rake +43 -0
- data/tasks/databases.rake +12 -0
- data/tasks/databases/mysql.rake +30 -0
- data/tasks/databases/oracle.rake +25 -0
- data/tasks/databases/postgresql.rake +25 -0
- data/tasks/databases/sqlite3.rake +28 -0
- data/tasks/deployment.rake +22 -0
- data/tasks/local_setup.rake +13 -0
- data/tasks/website.rake +18 -0
- data/test/README_tests.txt +67 -0
- data/test/abstract_unit.rb +103 -0
- data/test/connections/native_ibm_db/connection.rb +23 -0
- data/test/connections/native_mysql/connection.rb +13 -0
- data/test/connections/native_oracle/connection.rb +14 -0
- data/test/connections/native_oracle_enhanced/connection.rb +20 -0
- data/test/connections/native_postgresql/connection.rb +8 -0
- data/test/connections/native_sqlite/connection.rb +9 -0
- data/test/fixtures/article.rb +5 -0
- data/test/fixtures/article_group.rb +4 -0
- data/test/fixtures/article_groups.yml +7 -0
- data/test/fixtures/articles.yml +6 -0
- data/test/fixtures/comment.rb +6 -0
- data/test/fixtures/comments.yml +16 -0
- data/test/fixtures/db_definitions/db2-create-tables.sql +113 -0
- data/test/fixtures/db_definitions/db2-drop-tables.sql +16 -0
- data/test/fixtures/db_definitions/mysql.sql +181 -0
- data/test/fixtures/db_definitions/oracle.drop.sql +39 -0
- data/test/fixtures/db_definitions/oracle.sql +188 -0
- data/test/fixtures/db_definitions/postgresql.sql +206 -0
- data/test/fixtures/db_definitions/sqlite.sql +166 -0
- data/test/fixtures/department.rb +5 -0
- data/test/fixtures/departments.yml +3 -0
- data/test/fixtures/dorm.rb +3 -0
- data/test/fixtures/dorms.yml +2 -0
- data/test/fixtures/employee.rb +4 -0
- data/test/fixtures/employees.yml +9 -0
- data/test/fixtures/group.rb +3 -0
- data/test/fixtures/groups.yml +3 -0
- data/test/fixtures/hack.rb +6 -0
- data/test/fixtures/hacks.yml +2 -0
- data/test/fixtures/kitchen_sink.rb +3 -0
- data/test/fixtures/kitchen_sinks.yml +5 -0
- data/test/fixtures/membership.rb +10 -0
- data/test/fixtures/membership_status.rb +3 -0
- data/test/fixtures/membership_statuses.yml +10 -0
- data/test/fixtures/memberships.yml +6 -0
- data/test/fixtures/product.rb +7 -0
- data/test/fixtures/product_tariff.rb +5 -0
- data/test/fixtures/product_tariffs.yml +12 -0
- data/test/fixtures/products.yml +6 -0
- data/test/fixtures/reading.rb +4 -0
- data/test/fixtures/readings.yml +10 -0
- data/test/fixtures/reference_code.rb +7 -0
- data/test/fixtures/reference_codes.yml +28 -0
- data/test/fixtures/reference_type.rb +7 -0
- data/test/fixtures/reference_types.yml +9 -0
- data/test/fixtures/restaurant.rb +6 -0
- data/test/fixtures/restaurants.yml +5 -0
- data/test/fixtures/restaurants_suburbs.yml +11 -0
- data/test/fixtures/room.rb +10 -0
- data/test/fixtures/room_assignment.rb +4 -0
- data/test/fixtures/room_assignments.yml +4 -0
- data/test/fixtures/room_attribute.rb +3 -0
- data/test/fixtures/room_attribute_assignment.rb +5 -0
- data/test/fixtures/room_attribute_assignments.yml +4 -0
- data/test/fixtures/room_attributes.yml +3 -0
- data/test/fixtures/rooms.yml +3 -0
- data/test/fixtures/seat.rb +5 -0
- data/test/fixtures/seats.yml +4 -0
- data/test/fixtures/street.rb +3 -0
- data/test/fixtures/streets.yml +15 -0
- data/test/fixtures/student.rb +4 -0
- data/test/fixtures/students.yml +2 -0
- data/test/fixtures/suburb.rb +6 -0
- data/test/fixtures/suburbs.yml +9 -0
- data/test/fixtures/tariff.rb +6 -0
- data/test/fixtures/tariffs.yml +13 -0
- data/test/fixtures/user.rb +10 -0
- data/test/fixtures/users.yml +6 -0
- data/test/hash_tricks.rb +34 -0
- data/test/plugins/pagination.rb +405 -0
- data/test/plugins/pagination_helper.rb +135 -0
- data/test/test_associations.rb +178 -0
- data/test/test_attribute_methods.rb +22 -0
- data/test/test_attributes.rb +80 -0
- data/test/test_clone.rb +34 -0
- data/test/test_composite_arrays.rb +32 -0
- data/test/test_create.rb +68 -0
- data/test/test_delete.rb +83 -0
- data/test/test_exists.rb +25 -0
- data/test/test_find.rb +73 -0
- data/test/test_ids.rb +90 -0
- data/test/test_miscellaneous.rb +39 -0
- data/test/test_pagination.rb +38 -0
- data/test/test_polymorphic.rb +32 -0
- data/test/test_santiago.rb +27 -0
- data/test/test_suite.rb +19 -0
- data/test/test_tutorial_example.rb +26 -0
- data/test/test_update.rb +40 -0
- data/test/test_validations.rb +11 -0
- data/website/index.html +195 -0
- data/website/index.txt +159 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +126 -0
- data/website/template.js +3 -0
- data/website/template.rhtml +53 -0
- data/website/version-raw.js +3 -0
- data/website/version-raw.txt +2 -0
- data/website/version.js +4 -0
- data/website/version.txt +3 -0
- metadata +339 -0
|
@@ -0,0 +1,84 @@
|
|
|
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
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
class CompositeKeyError < StandardError #:nodoc:
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class Base
|
|
6
|
+
INVALID_FOR_COMPOSITE_KEYS = 'Not appropriate for composite primary keys'
|
|
7
|
+
NOT_IMPLEMENTED_YET = 'Not implemented for composite primary keys yet'
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def set_primary_keys(*keys)
|
|
11
|
+
keys = keys.first if keys.first.is_a?(Array)
|
|
12
|
+
|
|
13
|
+
if keys.length == 1
|
|
14
|
+
set_primary_key(keys.first)
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
cattr_accessor :primary_keys
|
|
19
|
+
self.primary_keys = keys.map { |k| k.to_sym }
|
|
20
|
+
|
|
21
|
+
class_eval <<-EOV
|
|
22
|
+
extend CompositeClassMethods
|
|
23
|
+
include CompositeInstanceMethods
|
|
24
|
+
include CompositePrimaryKeys::ActiveRecord::AssociationPreload
|
|
25
|
+
EOV
|
|
26
|
+
|
|
27
|
+
class << unscoped
|
|
28
|
+
include CompositePrimaryKeys::ActiveRecord::FinderMethods::InstanceMethods
|
|
29
|
+
include CompositePrimaryKeys::ActiveRecord::Relation::InstanceMethods
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def composite?
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def composite?
|
|
39
|
+
self.class.composite?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def [](attr_name)
|
|
43
|
+
# CPK
|
|
44
|
+
if attr_name.is_a?(String) and attr_name != attr_name.split(CompositePrimaryKeys::ID_SEP).first
|
|
45
|
+
attr_name = attr_name.split(CompositePrimaryKeys::ID_SEP)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# CPK
|
|
49
|
+
if attr_name.is_a?(Array)
|
|
50
|
+
attr_name.map {|name| read_attribute(name)}
|
|
51
|
+
else
|
|
52
|
+
read_attribute(attr_name)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def []=(attr_name, value)
|
|
57
|
+
# CPK
|
|
58
|
+
if attr_name.is_a?(String) and attr_name != attr_name.split(CompositePrimaryKeys::ID_SEP).first
|
|
59
|
+
attr_name = attr_name.split(CompositePrimaryKeys::ID_SEP)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if attr_name.is_a? Array
|
|
63
|
+
unless value.length == attr_name.length
|
|
64
|
+
raise "Number of attr_names and values do not match"
|
|
65
|
+
end
|
|
66
|
+
[attr_name, value].transpose.map {|name,val| write_attribute(name, val)}
|
|
67
|
+
value
|
|
68
|
+
else
|
|
69
|
+
write_attribute(attr_name, value)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
module CompositeClassMethods
|
|
74
|
+
def primary_key
|
|
75
|
+
primary_keys
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def primary_key=(keys)
|
|
79
|
+
primary_keys = keys
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def composite?
|
|
83
|
+
true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"
|
|
87
|
+
#ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"
|
|
88
|
+
def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
|
|
89
|
+
many_ids.map {|ids| "#{left_bracket}#{ids}#{right_bracket}"}.join(list_sep)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
module CompositeInstanceMethods
|
|
94
|
+
# A model instance's primary keys is always available as model.ids
|
|
95
|
+
# whether you name it the default 'id' or set it to something else.
|
|
96
|
+
def id
|
|
97
|
+
attr_names = self.class.primary_keys
|
|
98
|
+
CompositePrimaryKeys::CompositeKeys.new(attr_names.map { |attr_name| read_attribute(attr_name) })
|
|
99
|
+
end
|
|
100
|
+
alias_method :ids, :id
|
|
101
|
+
|
|
102
|
+
def ids_hash
|
|
103
|
+
self.class.primary_key.zip(ids).inject(Hash.new) do |hash, (key, value)|
|
|
104
|
+
hash[key] = value
|
|
105
|
+
hash
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def to_param
|
|
110
|
+
id.to_s
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def id_before_type_cast #:nodoc:
|
|
114
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::NOT_IMPLEMENTED_YET
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def quoted_id #:nodoc:
|
|
118
|
+
[self.class.primary_keys, ids].
|
|
119
|
+
transpose.
|
|
120
|
+
map {|attr_name,id| quote_value(id, column_for_attribute(attr_name))}.
|
|
121
|
+
to_composite_ids
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Sets the primary ID.
|
|
125
|
+
def id=(ids)
|
|
126
|
+
ids = ids.split(CompositePrimaryKeys::ID_SEP) if ids.is_a?(String)
|
|
127
|
+
ids.flatten!
|
|
128
|
+
unless ids.is_a?(Array) and ids.length == self.class.primary_keys.length
|
|
129
|
+
raise "#{self.class}.id= requires #{self.class.primary_keys.length} ids"
|
|
130
|
+
end
|
|
131
|
+
[primary_keys, ids].transpose.each {|key, an_id| write_attribute(key , an_id)}
|
|
132
|
+
id
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def update_attribute(name, value)
|
|
136
|
+
raise ActiveRecordError, "#{name.to_s} is marked as readonly" if self.class.readonly_attributes.include? name.to_s
|
|
137
|
+
|
|
138
|
+
changes = {}
|
|
139
|
+
|
|
140
|
+
if name
|
|
141
|
+
name = name.to_s
|
|
142
|
+
send("#{name}=", value)
|
|
143
|
+
changes[name] = read_attribute(name)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
@changed_attributes.except!(*changes.keys)
|
|
147
|
+
self.class.update_all(changes, ids_hash) == 1
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Cloned objects have no id assigned and are treated as new records. Note that this is a "shallow" clone
|
|
151
|
+
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
|
|
152
|
+
# application specific and is therefore left to the application to implement according to its need.
|
|
153
|
+
def initialize_copy(other)
|
|
154
|
+
# Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
|
|
155
|
+
# deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
|
|
156
|
+
# over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
|
|
157
|
+
# For example in the test suite the topic model's after_initialize method sets the author_email_address to
|
|
158
|
+
# test@test.com. I would have thought this would mean that all cloned models would have an author email address
|
|
159
|
+
# of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
|
|
160
|
+
# after_initialize callback has to be run *before* the copying of the atrributes rather than afterwards in order
|
|
161
|
+
# for all tests to pass. This makes no sense to me.
|
|
162
|
+
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
|
|
163
|
+
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
|
|
164
|
+
# CPK
|
|
165
|
+
#cloned_attributes.delete(self.class.primary_key)
|
|
166
|
+
self.class.primary_key.each {|key| cloned_attributes.delete(key.to_s)}
|
|
167
|
+
|
|
168
|
+
@attributes = cloned_attributes
|
|
169
|
+
clear_aggregation_cache
|
|
170
|
+
@attributes_cache = {}
|
|
171
|
+
@new_record = true
|
|
172
|
+
ensure_proper_type
|
|
173
|
+
|
|
174
|
+
if scope = self.class.send(:current_scoped_methods)
|
|
175
|
+
create_with = scope.scope_for_create
|
|
176
|
+
create_with.each { |att,value| self.send("#{att}=", value) } if create_with
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def destroy
|
|
181
|
+
if persisted?
|
|
182
|
+
# CPK
|
|
183
|
+
# self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
|
|
184
|
+
self.class.unscoped.where(ids_hash).delete_all
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
@destroyed = true
|
|
188
|
+
freeze
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def update(attribute_names = @attributes.keys)
|
|
192
|
+
attributes_with_values = arel_attributes_values(false, false, attribute_names)
|
|
193
|
+
return 0 if attributes_with_values.empty?
|
|
194
|
+
# CPK
|
|
195
|
+
# self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
|
|
196
|
+
self.class.unscoped.where(ids_hash).arel.update(attributes_with_values)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module CompositePrimaryKeys
|
|
2
|
+
ID_SEP = ','
|
|
3
|
+
ID_SET_SEP = ';'
|
|
4
|
+
|
|
5
|
+
module ArrayExtension
|
|
6
|
+
def to_composite_keys
|
|
7
|
+
CompositeKeys.new(self)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_composite_ids
|
|
11
|
+
CompositeIds.new(self)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class CompositeKeys < Array
|
|
16
|
+
def to_s
|
|
17
|
+
# Doing this makes it easier to parse Base#[](attr_name)
|
|
18
|
+
join(ID_SEP)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class CompositeIds < Array
|
|
23
|
+
def to_s
|
|
24
|
+
join(ID_SEP)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Array.send(:include, CompositePrimaryKeys::ArrayExtension)
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
class PostgreSQLAdapter < 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 = columns.map { |c| "CAST(#{c} AS varchar)" }
|
|
12
|
+
"(#{columns.join('||')})"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Executes an INSERT query and returns the new record's ID
|
|
16
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
|
17
|
+
# Extract the table from the insert sql. Yuck.
|
|
18
|
+
table = sql.split(" ", 4)[2].gsub('"', '')
|
|
19
|
+
|
|
20
|
+
# Try an insert with 'returning id' if available (PG >= 8.2)
|
|
21
|
+
if supports_insert_with_returning?
|
|
22
|
+
pk, sequence_name = *pk_and_sequence_for(table) unless pk
|
|
23
|
+
if pk
|
|
24
|
+
quoted_pk = if pk.is_a?(Array)
|
|
25
|
+
pk.map { |col| quote_column_name(col) }.join(CompositePrimaryKeys::ID_SEP)
|
|
26
|
+
else
|
|
27
|
+
quote_column_name(pk)
|
|
28
|
+
end
|
|
29
|
+
id = select_value("#{sql} RETURNING #{quoted_pk}")
|
|
30
|
+
clear_query_cache
|
|
31
|
+
return id
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Otherwise, insert then grab last_insert_id.
|
|
36
|
+
if insert_id = super
|
|
37
|
+
insert_id
|
|
38
|
+
else
|
|
39
|
+
# If neither pk nor sequence name is given, look them up.
|
|
40
|
+
unless pk || sequence_name
|
|
41
|
+
pk, sequence_name = *pk_and_sequence_for(table)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# If a pk is given, fallback to default sequence name.
|
|
45
|
+
# Don't fetch last insert id for a table without a pk.
|
|
46
|
+
if pk && sequence_name ||= default_sequence_name(table, pk)
|
|
47
|
+
last_insert_id(table, sequence_name)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'active_record/connection_adapters/sqlite_adapter'
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters #:nodoc:
|
|
5
|
+
class SQLite3Adapter < SQLiteAdapter # :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
|