activerecord-postgresql-extensions 0.0.7
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/MIT-LICENSE +23 -0
- data/README.rdoc +32 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/lib/activerecord-postgresql-extensions.rb +30 -0
- data/lib/postgresql_extensions/foreign_key_associations.rb +367 -0
- data/lib/postgresql_extensions/postgresql_adapter_extensions.rb +646 -0
- data/lib/postgresql_extensions/postgresql_constraints.rb +579 -0
- data/lib/postgresql_extensions/postgresql_functions.rb +345 -0
- data/lib/postgresql_extensions/postgresql_geometry.rb +212 -0
- data/lib/postgresql_extensions/postgresql_indexes.rb +219 -0
- data/lib/postgresql_extensions/postgresql_languages.rb +80 -0
- data/lib/postgresql_extensions/postgresql_permissions.rb +322 -0
- data/lib/postgresql_extensions/postgresql_rules.rb +112 -0
- data/lib/postgresql_extensions/postgresql_schemas.rb +49 -0
- data/lib/postgresql_extensions/postgresql_sequences.rb +222 -0
- data/lib/postgresql_extensions/postgresql_tables.rb +308 -0
- data/lib/postgresql_extensions/postgresql_triggers.rb +131 -0
- data/lib/postgresql_extensions/postgresql_types.rb +17 -0
- data/lib/postgresql_extensions/postgresql_views.rb +103 -0
- data/postgresql-extensions.gemspec +50 -0
- data/test/adapter_test.rb +45 -0
- data/test/constraints_test.rb +98 -0
- data/test/functions_test.rb +112 -0
- data/test/geometry_test.rb +43 -0
- data/test/index_test.rb +68 -0
- data/test/languages_test.rb +48 -0
- data/test/permissions_test.rb +163 -0
- data/test/rules_test.rb +32 -0
- data/test/schemas_test.rb +43 -0
- data/test/sequences_test.rb +90 -0
- data/test/tables_test.rb +49 -0
- data/test/test_helper.rb +64 -0
- metadata +97 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2011 2167961 Ontario Inc., Zoocasa <code@zoocasa.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
= PostgreSQL ActiveRecord Extensions
|
3
|
+
|
4
|
+
What we've got here is a collection of extensions to the ActiveRecord
|
5
|
+
PostgreSQL adapter including:
|
6
|
+
|
7
|
+
* support for constraints in migrations including CHECK constraints, foreign
|
8
|
+
keys, unique constraints and so forth. Constraints can be added per column
|
9
|
+
and per table.
|
10
|
+
|
11
|
+
* some methods for manipulating all sorts of stuff like procedural languages,
|
12
|
+
functions, sequences, views, schemas and triggers.
|
13
|
+
|
14
|
+
* better support for creating indexes with expressions so you can use functions
|
15
|
+
and GIST and GIN indexes.
|
16
|
+
|
17
|
+
* support for manipulating role privileges.
|
18
|
+
|
19
|
+
* support for some PostgreSQL-specific statements like INHERITS and LIKE when
|
20
|
+
creating tables.
|
21
|
+
|
22
|
+
* support for automatically detecting foreign key relationships and building
|
23
|
+
ActiveRecord associations accordingly. This associations can be overriden
|
24
|
+
as necessary and are sane enough that they won't try to overwrite existing
|
25
|
+
associations.
|
26
|
+
|
27
|
+
== First Public Release Notice!
|
28
|
+
|
29
|
+
This is the first public release of this gem, so while it has been kind to us
|
30
|
+
and our software, we can't guarantee that for everyone. That said, we do hope
|
31
|
+
that someone out there finds it useful and enjoys the new-found powers of
|
32
|
+
PostgreSQL!
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rubygems/package_task'
|
6
|
+
require 'rake/testtask'
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
$:.push 'lib'
|
10
|
+
|
11
|
+
version = File.read('VERSION') rescue ''
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
gem.name = "activerecord-postgresql-extensions"
|
17
|
+
gem.summary = "A whole bunch of extensions the ActiveRecord PostgreSQL adapter."
|
18
|
+
gem.description = gem.summary
|
19
|
+
gem.email = "code@zoocasa.com"
|
20
|
+
gem.homepage = "http://github.com/zoocasa/activerecord-postgresql-extensions"
|
21
|
+
gem.authors = [ "J Smith" ]
|
22
|
+
end
|
23
|
+
Jeweler::GemcutterTasks.new
|
24
|
+
rescue LoadError
|
25
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'Test PostgreSQL extensions'
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.pattern = 'test/**/*_test.rb'
|
31
|
+
t.verbose = false
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Build docs'
|
35
|
+
Rake::RDocTask.new do |t|
|
36
|
+
require 'rdoc'
|
37
|
+
t.title = "ActiveRecord PostgreSQL Extensions #{version}"
|
38
|
+
t.main = 'README.rdoc'
|
39
|
+
t.rdoc_dir = 'doc'
|
40
|
+
t.rdoc_files.include('README.rdoc', 'MIT-LICENSE', 'lib/**/*.rb')
|
41
|
+
end
|
42
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.7
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
3
|
+
|
4
|
+
module PostgreSQLExtensions
|
5
|
+
end
|
6
|
+
|
7
|
+
dirname = File.join(File.dirname(__FILE__), 'postgresql_extensions')
|
8
|
+
|
9
|
+
%w{
|
10
|
+
postgresql_adapter_extensions
|
11
|
+
postgresql_constraints
|
12
|
+
postgresql_tables
|
13
|
+
postgresql_indexes
|
14
|
+
postgresql_permissions
|
15
|
+
postgresql_schemas
|
16
|
+
postgresql_languages
|
17
|
+
postgresql_rules
|
18
|
+
postgresql_functions
|
19
|
+
postgresql_sequences
|
20
|
+
postgresql_triggers
|
21
|
+
postgresql_views
|
22
|
+
postgresql_geometry
|
23
|
+
postgresql_types
|
24
|
+
foreign_key_associations
|
25
|
+
}.each do |file|
|
26
|
+
require File.join(dirname, file)
|
27
|
+
end
|
28
|
+
|
29
|
+
ActiveRecord::Base.send(:include, PostgreSQLExtensions::ActiveRecord::ForeignKeyAssociations)
|
30
|
+
|
@@ -0,0 +1,367 @@
|
|
1
|
+
|
2
|
+
module PostgreSQLExtensions::ActiveRecord
|
3
|
+
# The ForeignKeyAssociations module attempts to automatically create
|
4
|
+
# associations based on your database schema by looking at foreign key
|
5
|
+
# relationships. It can be enabled by setting the
|
6
|
+
# enable_foreign_key_associations configuration option on
|
7
|
+
# ActiveRecord::Base to true.
|
8
|
+
#
|
9
|
+
# The ForeignKeyAssociations isn't a replacement for hand-coded
|
10
|
+
# associations, as it specifically won't override any associations you
|
11
|
+
# create in your models, but can serve to keep your models a little more
|
12
|
+
# up-to-date by using the database itself as a means to creating
|
13
|
+
# associations.
|
14
|
+
#
|
15
|
+
# Foreign key associations are formed by looking at various system tables
|
16
|
+
# in your database and attempting to make sane decisions based on how
|
17
|
+
# foreign key relationships and indexes are created. We basically go by the
|
18
|
+
# following rules:
|
19
|
+
#
|
20
|
+
# * a foreign key reference will create a belongs_to on the model doing
|
21
|
+
# the referencing as well as either a has_one or has_many association
|
22
|
+
# on the referenced table. If there is a UNIQUE index on the foreign key
|
23
|
+
# column, we use a has_one association; otherwise, we use a has_many.
|
24
|
+
# * "has_many :through" associations are found using multi-column UNIQUE
|
25
|
+
# indexes and existing associations which are either found during the
|
26
|
+
# first stages of our process or are pre-existing.
|
27
|
+
#
|
28
|
+
# Using PostgreSQL as an example:
|
29
|
+
#
|
30
|
+
# CREATE TABLE "foos" (
|
31
|
+
# id serial NOT NULL PRIMARY KEY
|
32
|
+
# );
|
33
|
+
#
|
34
|
+
# CREATE TABLE "bars" (
|
35
|
+
# id serial NOT NULL PRIMARY KEY,
|
36
|
+
# foo_id integer NOT NULL REFERENCES "foo"("id")
|
37
|
+
# );
|
38
|
+
#
|
39
|
+
# In this case, we will attempt to create the following associations:
|
40
|
+
#
|
41
|
+
# # Foo model:
|
42
|
+
# has_many :bars
|
43
|
+
#
|
44
|
+
# # Bar model:
|
45
|
+
# belongs_to :foo
|
46
|
+
#
|
47
|
+
# If we were to add a UNIQUE index on the foo_id column in bars, we
|
48
|
+
# would get a has_one assocation in the foos model:
|
49
|
+
#
|
50
|
+
# CREATE TABLE "bars" (
|
51
|
+
# id serial NOT NULL PRIMARY KEY,
|
52
|
+
# foo_id integer NOT NULL REFERENCES "foo"("id") UNIQUE
|
53
|
+
# );
|
54
|
+
# # or ALTER TABLE or CREATE UNIQUE INDEX, whatever
|
55
|
+
#
|
56
|
+
# Produces the following associations:
|
57
|
+
#
|
58
|
+
# # Foo model:
|
59
|
+
# has_one :bars
|
60
|
+
#
|
61
|
+
# # Bar model:
|
62
|
+
# belongs_to :foo
|
63
|
+
#
|
64
|
+
# We also attempt to do "has_many :through" associations by looking for
|
65
|
+
# things like UNIQUE indexes on multiple columns, previously existing
|
66
|
+
# associations and model names. For instance, given the following
|
67
|
+
# schema:
|
68
|
+
#
|
69
|
+
# CREATE TABLE "foos" (
|
70
|
+
# id serial NOT NULL PRIMARY KEY
|
71
|
+
# );
|
72
|
+
#
|
73
|
+
# CREATE TABLE "bars" (
|
74
|
+
# id serial NOT NULL PRIMARY KEY
|
75
|
+
# );
|
76
|
+
#
|
77
|
+
# CREATE TABLE "foo_bars" (
|
78
|
+
# id serial NOT NULL PRIMARY KEY,
|
79
|
+
# foo_id integer NOT NULL REFERENCES "foos"("id"),
|
80
|
+
# bar_id integer NOT NULL REFERENCES "bars"("id"),
|
81
|
+
# UNIQUE ("foo_id", "bar_id")
|
82
|
+
# );
|
83
|
+
#
|
84
|
+
# Would create the following associations:
|
85
|
+
#
|
86
|
+
# # FooBar model:
|
87
|
+
# belongs_to :foo
|
88
|
+
# belongs_to :bar
|
89
|
+
#
|
90
|
+
# # Foo model:
|
91
|
+
# has_many :foo_bars
|
92
|
+
# has_many :bars, :through => :foo_bars
|
93
|
+
#
|
94
|
+
# # Bar model:
|
95
|
+
# has_many :foo_bars
|
96
|
+
# has_many :foos, :through => :foo_bars
|
97
|
+
#
|
98
|
+
# The rules for association creation through foreign keys are fairly lax,
|
99
|
+
# i.e. you don't need to name your keys "something_id" as Rails generally
|
100
|
+
# demands by default. About the only thing that would really help us find
|
101
|
+
# foreign key associations is the naming used by your models: if they
|
102
|
+
# don't match up with the Rails conventions for model-to-table mapping
|
103
|
+
# (pluralization, underscores, etc.), we can get confused and may miss
|
104
|
+
# some associations. The associations will eventually be created once
|
105
|
+
# all of your models are loaded, but as of Rails 2.0 we can't guarantee
|
106
|
+
# when and if all of your models will load before we try to find our
|
107
|
+
# foreign keys, so bear that in mind when using this plugin. The only time
|
108
|
+
# this really comes up is when you're using set_table_name in a model to
|
109
|
+
# override the Rails conventions and we can't figure that out during our
|
110
|
+
# foreign key hunt.
|
111
|
+
#
|
112
|
+
# Note that this plugin will never try to override existing associations.
|
113
|
+
# If you have an existing association with the same name as one that we
|
114
|
+
# are trying to create (or for that matter, a method with the same name)
|
115
|
+
# then we will just silently and happily skip that association.
|
116
|
+
#
|
117
|
+
# Portions of this plugin were inspired by the RedHill on Rails plugins
|
118
|
+
# available at http://www.redhillonrails.org/. The idea is basically
|
119
|
+
# the same in both cases, although our implementations are rather
|
120
|
+
# different both in terms of structure and functionality, as this plugin
|
121
|
+
# is more specific to our particular needs.
|
122
|
+
module ForeignKeyAssociations
|
123
|
+
def self.included(base)
|
124
|
+
base.extend(ClassMethods)
|
125
|
+
end
|
126
|
+
|
127
|
+
module ClassMethods
|
128
|
+
def self.extended(base)
|
129
|
+
class << base
|
130
|
+
alias_method_chain :allocate, :foreign_keys
|
131
|
+
alias_method_chain :new, :foreign_keys
|
132
|
+
alias_method_chain :reflections, :foreign_keys
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def allocate_with_foreign_keys #:nodoc:
|
137
|
+
load_foreign_key_associations if load_foreign_key_associations? && !@foreign_key_associations_loaded
|
138
|
+
allocate_without_foreign_keys
|
139
|
+
end
|
140
|
+
|
141
|
+
def new_with_foreign_keys(*args) #:nodoc:
|
142
|
+
load_foreign_key_associations if load_foreign_key_associations? && !@foreign_key_associations_loaded
|
143
|
+
new_without_foreign_keys(*args) { |*block_args|
|
144
|
+
yield(*block_args) if block_given?
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
def reflections_with_foreign_keys #:nodoc:
|
149
|
+
load_foreign_key_associations if load_foreign_key_associations? && !@foreign_key_associations_loaded
|
150
|
+
reflections_without_foreign_keys
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns an Array of foreign keys referencing this model. See
|
154
|
+
# ActiveRecord::Base#referenced_foreign_keys for details.
|
155
|
+
def referenced_foreign_keys
|
156
|
+
@referenced_foreign_keys ||= connection.referenced_foreign_keys(table_name, "#{name} Referenced Foreign Keys")
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns an Array of foreign keys in this model. See
|
160
|
+
# ActiveRecord::Base#foreign_keys for details.
|
161
|
+
def foreign_keys
|
162
|
+
@foreign_keys ||= connection.foreign_keys(table_name, "#{name} Foreign Keys")
|
163
|
+
end
|
164
|
+
|
165
|
+
# Allows you to selectively disable foreign key association loading
|
166
|
+
# when the ActiveRecord setting enable_foreign_key_associations
|
167
|
+
# is enabled. This works on a per-model basis, and prevents any
|
168
|
+
# foreign key associations from being created on this model. This
|
169
|
+
# applies to both foreign keys that reference this model as well
|
170
|
+
# as foreign keys within the model itself.
|
171
|
+
def dont_load_foreign_key_associations!
|
172
|
+
@load_foreign_key_associations = false
|
173
|
+
end
|
174
|
+
|
175
|
+
# Creates foreign key associations for the model. This is
|
176
|
+
# essentially a three-step process:
|
177
|
+
#
|
178
|
+
# 1. Find any tables that reference this model via foreign keys
|
179
|
+
# and create the associations accordingly.
|
180
|
+
# 2. Find any foreign keys in this model and create the
|
181
|
+
# associations accordingly. This process creates both belongs_to
|
182
|
+
# associations on this model to the referenced models as well
|
183
|
+
# as has_many/has_one associations on the referenced models
|
184
|
+
# themselves. To determine whether the association is a has_many
|
185
|
+
# or a has_one, we take a look at UNIQUE indexes created on the
|
186
|
+
# table column. In cases where the index is UNIQUE, we create
|
187
|
+
# a has_one association; in all others, we create a has_many
|
188
|
+
# association.
|
189
|
+
# 3. Look at the model itself and try to determine whether or not
|
190
|
+
# we have a "has_many :through" association. We make this
|
191
|
+
# determination by looking to see if there are two foreign
|
192
|
+
# keys with the following conditions:
|
193
|
+
# * the model has an index with exactly two columns in it and
|
194
|
+
# the index itself is UNIQUE;
|
195
|
+
# * we've already created a belongs_to association with each
|
196
|
+
# column and the column names match the columns in the UNIQUE
|
197
|
+
# index; and
|
198
|
+
# * the model name is either "FirstModelSecondModel" or
|
199
|
+
# "SecondModelFirstModel".
|
200
|
+
# If these criteria match, then the "has_many :through"
|
201
|
+
# associations are created on both of the referenced models.
|
202
|
+
#
|
203
|
+
# In all cases, we respect any dont_load_foreign_key_associations!
|
204
|
+
# settings on individual models as well as take into account
|
205
|
+
# existing associations with the same names as the ones we're going
|
206
|
+
# to try to create. In other words, if you already have an
|
207
|
+
# association called :listings on a model and we find a foreign
|
208
|
+
# key that matches, we won't blow away your existing association
|
209
|
+
# and instead just continue along merrily.
|
210
|
+
def load_foreign_key_associations
|
211
|
+
return if @foreign_key_associations_loaded
|
212
|
+
@foreign_key_associations_loaded = true
|
213
|
+
|
214
|
+
indexes = connection.indexes(table_name, "#{name} Indexes")
|
215
|
+
|
216
|
+
# This does the associations for the tables that reference
|
217
|
+
# columns in this table.
|
218
|
+
referenced_foreign_keys.each do |fk|
|
219
|
+
begin
|
220
|
+
referencing_class = compute_type(fk[:table].classify)
|
221
|
+
referencing_class.load_foreign_key_associations if referencing_class.load_foreign_key_associations?
|
222
|
+
rescue NameError
|
223
|
+
# Do nothing. We won't bother creating associations
|
224
|
+
# if the model class itself doesn't exist.
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# This does the foreign key associations for this model.
|
229
|
+
foreign_keys.each do |fk|
|
230
|
+
belongs_to_association_id = fk[:table].singularize.to_sym
|
231
|
+
begin
|
232
|
+
references_class_name = fk[:table].classify
|
233
|
+
references_class = compute_type(references_class_name)
|
234
|
+
|
235
|
+
unless method_defined?(belongs_to_association_id)
|
236
|
+
belongs_to(
|
237
|
+
belongs_to_association_id,
|
238
|
+
:class_name => references_class_name,
|
239
|
+
:foreign_key => fk[:column]
|
240
|
+
)
|
241
|
+
end
|
242
|
+
|
243
|
+
# If we have a unique index for this column, we'll
|
244
|
+
# create a has_one association; otherwise, it's a
|
245
|
+
# has_many.
|
246
|
+
if indexes.detect { |i|
|
247
|
+
i.columns.length == 1 && i.unique && i.columns.include?(fk[:column])
|
248
|
+
}
|
249
|
+
has_association_id = self.name.demodulize.underscore.to_sym
|
250
|
+
unless references_class.method_defined?(has_association_id)
|
251
|
+
references_class.has_one(
|
252
|
+
has_association_id, {
|
253
|
+
:class_name => name,
|
254
|
+
:foreign_key => fk[:column]
|
255
|
+
}
|
256
|
+
)
|
257
|
+
end
|
258
|
+
else
|
259
|
+
has_association_id = self.name.demodulize.underscore.pluralize.to_sym
|
260
|
+
unless references_class.method_defined?(has_association_id)
|
261
|
+
references_class.has_many(
|
262
|
+
has_association_id, {
|
263
|
+
:class_name => name,
|
264
|
+
:foreign_key => fk[:column]
|
265
|
+
}
|
266
|
+
)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
rescue NameError
|
270
|
+
# Do nothing. NOTHING! We don't want to create
|
271
|
+
# associations on non-existent classes.
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# If we have an index that contains exactly two columns and
|
276
|
+
# it's a UNIQUE index, then we might have a
|
277
|
+
# "has_many :through" association, so let's look for it now.
|
278
|
+
if through = indexes.detect { |i| i.columns.length == 2 && i.unique }
|
279
|
+
catch :not_a_has_many_through do
|
280
|
+
hmt_associations = []
|
281
|
+
|
282
|
+
# This will loop through the columns in the UNIQUE
|
283
|
+
# index and see if they're both foreign keys
|
284
|
+
# referencing other tables.
|
285
|
+
through.columns.each do |c|
|
286
|
+
if foreign_keys.detect { |fk| fk[1] == c }.blank?
|
287
|
+
throw(:not_a_has_many_through)
|
288
|
+
end
|
289
|
+
|
290
|
+
# Check that both columns have belongs_to
|
291
|
+
# associations.
|
292
|
+
unless hmt_association = reflections.detect { |r, v|
|
293
|
+
v.macro == :belongs_to && v.primary_key_name == c
|
294
|
+
}
|
295
|
+
throw(:not_a_has_many_through)
|
296
|
+
end
|
297
|
+
|
298
|
+
hmt_associations << hmt_association
|
299
|
+
end
|
300
|
+
|
301
|
+
hmt_first = hmt_associations.first
|
302
|
+
hmt_second = hmt_associations.last
|
303
|
+
|
304
|
+
hmt_first_association_id = hmt_second.first.to_s.pluralize.to_sym
|
305
|
+
hmt_second_association_id = hmt_first.first.to_s.pluralize.to_sym
|
306
|
+
|
307
|
+
hmt_first_class = hmt_first.last.name.constantize
|
308
|
+
hmt_second_class = hmt_second.last.name.constantize
|
309
|
+
|
310
|
+
# Check to see if this model is named
|
311
|
+
# "FirstModelSecondModel" or "SecondModelFirstModel".
|
312
|
+
if strict_foreign_key_has_many_throughs
|
313
|
+
unless [
|
314
|
+
"#{hmt_first_class}#{hmt_second_class}",
|
315
|
+
"#{hmt_second_class}#{hmt_first_class}"
|
316
|
+
].include?(self.name)
|
317
|
+
throw(:not_a_has_many_through)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# If we haven't thrown up, we can create the
|
322
|
+
# associations, assuming they don't already exist and
|
323
|
+
# we're allowed to.
|
324
|
+
through_association_id = self.name.demodulize.underscore.pluralize.to_sym
|
325
|
+
|
326
|
+
if hmt_first_class.load_foreign_key_associations?
|
327
|
+
unless hmt_first_class.method_defined?(hmt_first_association_id)
|
328
|
+
hmt_first_class.has_many(
|
329
|
+
hmt_first_association_id,
|
330
|
+
:through => through_association_id
|
331
|
+
)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
if hmt_second_class.load_foreign_key_associations?
|
336
|
+
unless hmt_second_class.method_defined?(hmt_second_association_id)
|
337
|
+
hmt_second_class.has_many(
|
338
|
+
hmt_second_association_id,
|
339
|
+
:through => through_association_id
|
340
|
+
)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# Should we load a model's foreign key associations? Maybe we
|
348
|
+
# should, and maybe we shouldn't.
|
349
|
+
def load_foreign_key_associations?
|
350
|
+
ActiveRecord::Base.enable_foreign_key_associations &&
|
351
|
+
!abstract_class? && (
|
352
|
+
@load_foreign_key_associations.nil? || @load_foreign_key_associations
|
353
|
+
)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
ActiveRecord::Base.class_eval do
|
360
|
+
# Enable foreign key associations.
|
361
|
+
cattr_accessor :enable_foreign_key_associations
|
362
|
+
|
363
|
+
# Be a bit stricter when looking for "has_many :through" associations by
|
364
|
+
# checking the name of the through model for the Rails-like naming
|
365
|
+
# convention of "FirstModelSecondModel".
|
366
|
+
cattr_accessor :strict_foreign_key_has_many_throughs
|
367
|
+
end
|