schema_associations 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +1 -0
- data/README.md +385 -0
- data/lib/schema_associations.rb +8 -1
- data/lib/schema_associations/active_record/associations.rb +12 -2
- data/lib/schema_associations/version.rb +1 -1
- data/runspecs +1 -1
- data/spec/association_spec.rb +13 -0
- metadata +22 -38
- data/README.rdoc +0 -330
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 757e462b9762015289cb5f2fe1bbbf6bb1c566a4
|
4
|
+
data.tar.gz: 344a36f0f484b1c929e84f38a4c2c1daa9dbacbf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 67b28e20707ccac8c97fb0713face550b9c44adbca8102de6149c2fe8caf5326c389a5d9030df1f6ad23e0b3d43c3e1faf9db8d98c47ccd3d1adea67f142d118
|
7
|
+
data.tar.gz: 5d19049e8483c2751f48bb39bf2f09989185a80bfa8dc1d6536e79be60d1ceaab26f3dd5b8f91812d31f1f3cc3b6759a7a98f2896fd91f51c0a2014bbc3b5148
|
data/.travis.yml
CHANGED
data/README.md
ADDED
@@ -0,0 +1,385 @@
|
|
1
|
+
# SchemaAssociations
|
2
|
+
|
3
|
+
SchemaAssiciations is an ActiveRecord extension that keeps your model class
|
4
|
+
definitions simpler and more DRY, by automatically defining associations based
|
5
|
+
on the database schema.
|
6
|
+
|
7
|
+
[![Gem Version](https://badge.fury.io/rb/schema_associations.png)](http://badge.fury.io/rb/schema_associations)
|
8
|
+
[![Build Status](https://secure.travis-ci.org/lomba/schema_associations.png)](http://travis-ci.org/lomba/schema_associations)
|
9
|
+
[![Dependency Status](https://gemnasium.com/lomba/schema_associations.png)](https://gemnasium.com/lomba/schema_associations)
|
10
|
+
|
11
|
+
## Overview
|
12
|
+
|
13
|
+
One of the great things about Rails (ActiveRecord, in particular) is that it
|
14
|
+
inspects the database and automatically defines accessors for all your
|
15
|
+
columns, keeping your model class definitions simple and DRY. That's great
|
16
|
+
for simple data columns, but where it falls down is when your table contains
|
17
|
+
references to other tables: then the "accessors" you need are the associations
|
18
|
+
defined using `belongs_to`, `has_one`, `has_many`, and
|
19
|
+
`has_and_belongs_to_many` -- and you need to put them into your model class
|
20
|
+
definitions by hand. In fact, for every relation, you need to define two
|
21
|
+
associations each listing its inverse, such as
|
22
|
+
|
23
|
+
class Post < ActiveRecord::Base
|
24
|
+
has_many :comments, :inverse_of => :post
|
25
|
+
end
|
26
|
+
|
27
|
+
class Comment < ActiveReocrd::Base
|
28
|
+
belongs_to :post, :inverse_of => :comments
|
29
|
+
end
|
30
|
+
|
31
|
+
....which isn't so DRY.
|
32
|
+
|
33
|
+
Enter the SchemaAssociations gem. It extends ActiveRecord to automatically
|
34
|
+
define the appropriate associations based on foreign key constraints in the
|
35
|
+
database. SchemaAssociations builds on the
|
36
|
+
[schema_plus](http://rubygems.org/gems/schema_plus) gem that automatically
|
37
|
+
defines foreign key constraints. So the common case is simple -- if you have
|
38
|
+
this in your migration:
|
39
|
+
|
40
|
+
create_table :posts do |t|
|
41
|
+
end
|
42
|
+
|
43
|
+
create_table :comments do |t|
|
44
|
+
t.integer post_id
|
45
|
+
end
|
46
|
+
|
47
|
+
Then all you need for your models is:
|
48
|
+
|
49
|
+
class Post < ActiveRecord::Base
|
50
|
+
end
|
51
|
+
|
52
|
+
class Comment < ActiveRecord::Base
|
53
|
+
end
|
54
|
+
|
55
|
+
and SchemaAssociations defines the appropriate associations under the hood.
|
56
|
+
|
57
|
+
### What if I want something special?
|
58
|
+
|
59
|
+
You're always free to define associations yourself, if for example you want to
|
60
|
+
pass special options. SchemaAssociations won't clobber any existing
|
61
|
+
definitions.
|
62
|
+
|
63
|
+
You can also control the behavior with various options, globally via
|
64
|
+
SchemaAssociations::setup or per-model via
|
65
|
+
SchemaAssociations::ActiveRecord#schema_associations, such as:
|
66
|
+
|
67
|
+
class Post < ActiveRecord::Base
|
68
|
+
schema_associations :concise_names => false
|
69
|
+
end
|
70
|
+
|
71
|
+
See the[SchemaAssociations::Confg](http://rubydoc.info/gems/schema_associations/SchemaAssociations/Config) for the available options.
|
72
|
+
|
73
|
+
### This seems cool, but I'm worried about too much automagic
|
74
|
+
|
75
|
+
You can globally turn off automatic creation in
|
76
|
+
`config/initializers/schema_associations.rb`:
|
77
|
+
|
78
|
+
SchemaAssociations.setup do |config|
|
79
|
+
config.auto_create = false
|
80
|
+
end
|
81
|
+
|
82
|
+
Then in any model where you want automatic associations, just do
|
83
|
+
|
84
|
+
class Post < ActiveRecord::Base
|
85
|
+
schema_associations
|
86
|
+
end
|
87
|
+
|
88
|
+
You can also pass options as per above.
|
89
|
+
|
90
|
+
## Full Details
|
91
|
+
|
92
|
+
### The basics
|
93
|
+
|
94
|
+
The common cases work entirely as you'd expect. For a one-to-many
|
95
|
+
relationship using standard naming conventions:
|
96
|
+
|
97
|
+
# migration:
|
98
|
+
|
99
|
+
create_table :comments do |t|
|
100
|
+
t.integer post_id
|
101
|
+
end
|
102
|
+
|
103
|
+
# schema_associations defines:
|
104
|
+
|
105
|
+
class Post < ActiveRecord::Base
|
106
|
+
has_many :comments
|
107
|
+
end
|
108
|
+
|
109
|
+
class Comment < ActiveReocrd::Base
|
110
|
+
belongs_to :post
|
111
|
+
end
|
112
|
+
|
113
|
+
For a one-to-one relationship:
|
114
|
+
|
115
|
+
# migration:
|
116
|
+
|
117
|
+
create_table :comments do |t|
|
118
|
+
t.integer post_id, :index => :unique # (using the :index option provided by schema_plus )
|
119
|
+
end
|
120
|
+
|
121
|
+
# schema_associations defines:
|
122
|
+
|
123
|
+
class Post < ActiveRecord::Base
|
124
|
+
has_one :comment
|
125
|
+
end
|
126
|
+
|
127
|
+
class Comment < ActiveReocrd::Base
|
128
|
+
belongs_to :post
|
129
|
+
end
|
130
|
+
|
131
|
+
And for many-to-many relationships:
|
132
|
+
|
133
|
+
# migration:
|
134
|
+
|
135
|
+
create_table :groups_members do |t|
|
136
|
+
integer :group_id
|
137
|
+
integer :member_id
|
138
|
+
end
|
139
|
+
|
140
|
+
# schema_associations defines:
|
141
|
+
|
142
|
+
class Group < ActiveReocrd::Base
|
143
|
+
has_and_belongs_to_many :members
|
144
|
+
end
|
145
|
+
|
146
|
+
class Member < ActiveRecord::Base
|
147
|
+
has_and_belongs_to_many :groups
|
148
|
+
end
|
149
|
+
|
150
|
+
### Unusual names, multiple references
|
151
|
+
|
152
|
+
Sometimes you want or need to deviate from the simple naming conventions. In
|
153
|
+
this case, the `belongs_to` relationship name is taken from the name of the
|
154
|
+
foreign key column, and the `has_many` or `has_one` is named by the
|
155
|
+
referencing table, suffixed with "as" the relationship name. An example
|
156
|
+
should make this clear...
|
157
|
+
|
158
|
+
Suppose your company hires interns, and each intern is assigned a manager and
|
159
|
+
a mentor, who are regular employees.
|
160
|
+
|
161
|
+
create_table :interns do |t|
|
162
|
+
t.integer :manager_id, :references => :employees
|
163
|
+
t.integer :mentor_id, :references => :employees
|
164
|
+
end
|
165
|
+
|
166
|
+
SchemaAssociations defines a `belongs_to` association for each reference,
|
167
|
+
named according to the column:
|
168
|
+
|
169
|
+
class Intern < ActiveRecord::Base
|
170
|
+
belongs_to :manager, :class_name => "Employee", :foreign_key => "manager_id"
|
171
|
+
belongs_to :mentor, :class_name => "Employee", :foreign_key => "mentor_id"
|
172
|
+
end
|
173
|
+
|
174
|
+
And the corresponding `has_many` association each gets a suffix to indicate
|
175
|
+
which one relation it refers to:
|
176
|
+
|
177
|
+
class Employee < ActiveRecord::Base
|
178
|
+
has_many :interns_as_manager, :class_name => "Intern", :foreign_key => "manager_id"
|
179
|
+
has_many :interns_as_mentor, :class_name => "Intern", :foreign_key => "mentor_id"
|
180
|
+
end
|
181
|
+
|
182
|
+
### Special case for trees
|
183
|
+
|
184
|
+
If your forward relation is named "parent", SchemaAssociations names the
|
185
|
+
reverse relation "child" or "children". That is, if you have:
|
186
|
+
|
187
|
+
create_table :nodes
|
188
|
+
t.integer :parent_id # schema_plus assumes it's a reference to this table
|
189
|
+
end
|
190
|
+
|
191
|
+
Then SchemaAssociations will define
|
192
|
+
|
193
|
+
class Node < ActiveRecord::Base
|
194
|
+
belongs_to :parent, :class_name => "Node", :foreign_key => "parent_id"
|
195
|
+
has_many :children, :class_name => "Node", :foreign_key => "parent_id"
|
196
|
+
end
|
197
|
+
|
198
|
+
### Concise names
|
199
|
+
|
200
|
+
For modularity in your tables and classes, you might use a common prefix for
|
201
|
+
related objects. For example, you may have widgets each of which has a color,
|
202
|
+
and might have one base that has a top color and a bottom color, from the same
|
203
|
+
set of colors.
|
204
|
+
|
205
|
+
create_table :widget_colors |t|
|
206
|
+
end
|
207
|
+
|
208
|
+
create_table :widgets do |t|
|
209
|
+
t.integer :widget_color_id
|
210
|
+
end
|
211
|
+
|
212
|
+
create_table :widget_base
|
213
|
+
t.integer :widget_id, :index => :unique
|
214
|
+
t.integer :top_widget_color_id, :references => :widget_colors
|
215
|
+
t.integer :bottom_widget_color_id, :references => :widget_colors
|
216
|
+
end
|
217
|
+
|
218
|
+
Using the full name for the associations would make your code verbose and not
|
219
|
+
quite DRY:
|
220
|
+
|
221
|
+
@widget.widget_color
|
222
|
+
@widget.widget_base.top_widget_color
|
223
|
+
|
224
|
+
Instead, by default, SchemaAssociations uses concise names: shared leading
|
225
|
+
words are removed from the association name. So instead of the above, your
|
226
|
+
code looks like:
|
227
|
+
|
228
|
+
@widget.color
|
229
|
+
@widget.base.top_color
|
230
|
+
|
231
|
+
i.e. these associations would be defined:
|
232
|
+
|
233
|
+
class WidgetColor < ActiveRecord::Base
|
234
|
+
has_many :widgets, :class_name => "Widget", :foreign_key => "widget_color_id"
|
235
|
+
has_many :bases_as_top, :class_name => "WidgetBase", :foreign_key => "top_widget_color_id"
|
236
|
+
has_many :bases_as_bottom, :class_name => "WidgetBase", :foreign_key => "bottom_widget_color_id"
|
237
|
+
end
|
238
|
+
|
239
|
+
class Widget < ActiveRecord::Base
|
240
|
+
belongs_to :color, :class_name => "WidgetColor", :foreign_key => "widget_color_id"
|
241
|
+
has_one :base, :class_name => "WidgetBase", :foreign_key => "widget_base_id"
|
242
|
+
end
|
243
|
+
|
244
|
+
class WidgetBase < ActiveRecord::Base
|
245
|
+
belongs_to :top_color, :class_name => "WidgetColor", :foreign_key => "top_widget_color_id"
|
246
|
+
belongs_to :bottom_color, :class_name => "WidgetColor", :foreign_key => "bottom_widget_color_id"
|
247
|
+
belongs_to :widget, :class_name => "Widget", :foreign_key => "widget_id"
|
248
|
+
end
|
249
|
+
|
250
|
+
If you like the formality of using full names for the asociations, you can
|
251
|
+
turn off concise names globally or per-model, see [SchemaAssociations::Config](http://rubydoc.info/gems/schema_associations/SchemaAssociations/Config)
|
252
|
+
|
253
|
+
### Ordering `has_many` using `position`
|
254
|
+
|
255
|
+
If the target of a `has_many` association has a column named `position`,
|
256
|
+
SchemaAssociations will specify `:order => :position` for the association.
|
257
|
+
That is,
|
258
|
+
|
259
|
+
create_table :comments do |t|
|
260
|
+
t.integer post_id
|
261
|
+
t.integer position
|
262
|
+
end
|
263
|
+
|
264
|
+
leads to
|
265
|
+
|
266
|
+
class Post < ActiveRecord::Base
|
267
|
+
has_many :comments, :order => :position
|
268
|
+
end
|
269
|
+
|
270
|
+
## Table names and model class names
|
271
|
+
|
272
|
+
SchemaAssociations determins the mode class name from the table name using the same convention (and helpers) that ActiveRecord uses. But sometimes you might be doing things differently. For example, in an engine you might have a prefix that goes in front of all table names, and the models might all be in a namespace.
|
273
|
+
|
274
|
+
To that end, SchemaAssociations lets you configure mappings from a table name prefix to a model class name prefix to use instead. For example, suppose your database had tables:
|
275
|
+
|
276
|
+
hpy_campers
|
277
|
+
hpy_go_lucky
|
278
|
+
|
279
|
+
The default model class names would be
|
280
|
+
|
281
|
+
HpyCampers
|
282
|
+
HpyGoLucky
|
283
|
+
|
284
|
+
But if instead you wanted
|
285
|
+
|
286
|
+
Happy::Campers
|
287
|
+
Happy::GoLucky
|
288
|
+
|
289
|
+
You could set up this mapping in `config/initializers/schema_associations.rb`:
|
290
|
+
|
291
|
+
SchemaPlus.setup do |config|
|
292
|
+
config.table_prefix_map["hpy_"] = "Happy::"
|
293
|
+
end
|
294
|
+
|
295
|
+
Tables names that don't start with `hpy_` will continue to use the default determination.
|
296
|
+
|
297
|
+
You can set up multiple mappings. E.g. if you're using several engines they can each set up the mapping for their own moduels.
|
298
|
+
|
299
|
+
You can set up a mapping from or to the empty string, in order to unconditionally add or remove prefixes from all model class names.
|
300
|
+
|
301
|
+
|
302
|
+
## How do I know what it did?
|
303
|
+
|
304
|
+
If you're curious (or dubious) about what associations SchemaAssociations
|
305
|
+
defines, you can check the log file. For every assocation that
|
306
|
+
SchemaAssociations defines, it generates an info entry such as
|
307
|
+
|
308
|
+
[schema_associations] Post.has_many :comments, :class_name "Comment", :foreign_key "comment_id"
|
309
|
+
|
310
|
+
which shows the exact method definition call.
|
311
|
+
|
312
|
+
|
313
|
+
SchemaAssociations defines the associations lazily, only creating them when
|
314
|
+
they're first needed. So you may need to search through the log file to find
|
315
|
+
them all (and some may not be defined at all if they were never needed for the
|
316
|
+
use cases that you logged).
|
317
|
+
|
318
|
+
## Compatibility
|
319
|
+
|
320
|
+
SchemaAssociations supports all combinations of:
|
321
|
+
* rails 3.2
|
322
|
+
* MRI ruby 1.9.2 or 1.9.3
|
323
|
+
|
324
|
+
|
325
|
+
Note: As of version 1.0.0, ruby 1.8.7 and rails < 3.2 are no longer supported.
|
326
|
+
The last version to support them is 0.1.2
|
327
|
+
|
328
|
+
## Installation
|
329
|
+
|
330
|
+
Install from http://rubygems.org via
|
331
|
+
|
332
|
+
$ gem install "schema_associations"
|
333
|
+
|
334
|
+
or in a Gemfile
|
335
|
+
|
336
|
+
gem "schema_associations"
|
337
|
+
|
338
|
+
## Testing
|
339
|
+
|
340
|
+
SchemaAssociations is tested using rspec, sqlite3, and rvm, with some hackery
|
341
|
+
to test against multiple versions of rails and ruby. To run the full combo of
|
342
|
+
tests, after you've forked & cloned:
|
343
|
+
|
344
|
+
$ cd schema_associations
|
345
|
+
$ ./runspecs --install # do this once to install gem dependencies for all versions (slow)
|
346
|
+
$ ./runspecs # as many times as you like
|
347
|
+
|
348
|
+
See `./runspecs --help` for more options. In particular, to run rspec on a
|
349
|
+
specific file or example (rather than running the full suite) you can do, e.g.
|
350
|
+
|
351
|
+
$ ./runspecs [other options] --rspec -- spec/association_spec.rb -e 'base'
|
352
|
+
|
353
|
+
If you're running ruby 1.9, code coverage results will be in
|
354
|
+
coverage/index.html -- it should be at 100% coverage.
|
355
|
+
|
356
|
+
## Release notes:
|
357
|
+
|
358
|
+
### 1.1.0
|
359
|
+
|
360
|
+
* New feature: `config.table_prefix_map`
|
361
|
+
|
362
|
+
### 1.0.1
|
363
|
+
|
364
|
+
* Bug fix: use singular :inverse_of for :belongs_to of a :has_one
|
365
|
+
|
366
|
+
|
367
|
+
### 1.0.0
|
368
|
+
|
369
|
+
* Use :inverse_of in generated associations
|
370
|
+
|
371
|
+
* Drop support for ruby 1.8.7 and rails < 3.2
|
372
|
+
|
373
|
+
|
374
|
+
## History
|
375
|
+
|
376
|
+
* SchemaAssociations is derived from the "Red Hill On Rails" plugin
|
377
|
+
foreign_key_associations originally created by harukizaemon
|
378
|
+
(https://github.com/harukizaemon)
|
379
|
+
|
380
|
+
* SchemaAssociations was created in 2011 by Michal Lomnicki and Ronen Barzel
|
381
|
+
|
382
|
+
|
383
|
+
## License
|
384
|
+
|
385
|
+
This gem is released under the MIT license.
|
data/lib/schema_associations.rb
CHANGED
@@ -66,13 +66,20 @@ module SchemaAssociations
|
|
66
66
|
# +:has_and_belongs_to_many+, or +nil+. Default is +nil+.
|
67
67
|
has_value :only_type, :default => nil
|
68
68
|
|
69
|
+
##
|
70
|
+
# :attr_accessor: table_prefix_map
|
71
|
+
#
|
72
|
+
# Hash whose keys are possible matches at the start of table names, and
|
73
|
+
# whose corresponding values are the prefix to use in front of class
|
74
|
+
# names.
|
75
|
+
has_value :table_prefix_map, :default => {}
|
76
|
+
|
69
77
|
def dup #:nodoc:
|
70
78
|
self.class.new(Hash[attributes.collect{ |key, val| [key, Valuable === val ? val.class.new(val.attributes) : val] }])
|
71
79
|
end
|
72
80
|
|
73
81
|
def update_attributes(opts)#:nodoc:
|
74
82
|
opts = opts.dup
|
75
|
-
opts.keys.each { |key| self.send(key).update_attributes(opts.delete(key)) if self.class.attributes.include? key and Hash === opts[key] }
|
76
83
|
super(opts)
|
77
84
|
self
|
78
85
|
end
|
@@ -102,8 +102,8 @@ module SchemaAssociations
|
|
102
102
|
references_name = fk.references_table_name.singularize
|
103
103
|
referencing_name = referencing_table_name.singularize
|
104
104
|
|
105
|
-
referencing_class_name = referencing_name
|
106
|
-
references_class_name = references_name
|
105
|
+
referencing_class_name = _get_class_name(referencing_name)
|
106
|
+
references_class_name = _get_class_name(references_name)
|
107
107
|
|
108
108
|
names = _determine_association_names(column_name.sub(/_id$/, ''), referencing_name, references_name)
|
109
109
|
|
@@ -221,6 +221,16 @@ module SchemaAssociations
|
|
221
221
|
return true
|
222
222
|
end
|
223
223
|
|
224
|
+
def _get_class_name(name) #:nodoc:
|
225
|
+
name = name.dup
|
226
|
+
found = schema_associations_config.table_prefix_map.find { |table_prefix, class_prefix|
|
227
|
+
name.sub! %r[\A#{table_prefix}], ''
|
228
|
+
}
|
229
|
+
name = name.classify
|
230
|
+
name = found.last + name if found
|
231
|
+
name
|
232
|
+
end
|
233
|
+
|
224
234
|
def _method_exists?(name) #:nodoc:
|
225
235
|
method_defined?(name) || private_method_defined?(name) and not (name == :type && [Object, Kernel].include?(instance_method(:type).owner))
|
226
236
|
end
|
data/runspecs
CHANGED
data/spec/association_spec.rb
CHANGED
@@ -321,6 +321,19 @@ describe ActiveRecord::Base do
|
|
321
321
|
end
|
322
322
|
end
|
323
323
|
|
324
|
+
it "maps table prefix" do
|
325
|
+
with_associations_config(:table_prefix_map => { "wooga_" => "Happy"} ) do
|
326
|
+
create_tables(
|
327
|
+
"wooga_posts", {}, {},
|
328
|
+
"wooga_comments", {}, { :wooga_post_id => { :references => :wooga_posts} }
|
329
|
+
)
|
330
|
+
class HappyPost < ActiveRecord::Base ; self.table_name = 'wooga_posts' ; end
|
331
|
+
class HappyComment < ActiveRecord::Base ; self.table_name = 'wooga_comments' ; end
|
332
|
+
Kernel.warn HappyPost.reflect_on_all_associations.inspect
|
333
|
+
HappyComment.reflect_on_association(:post).class_name.should == "HappyPost"
|
334
|
+
HappyPost.reflect_on_association(:comments).class_name.should == "HappyComment"
|
335
|
+
end
|
336
|
+
end
|
324
337
|
|
325
338
|
context "with position" do
|
326
339
|
before(:each) do
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schema_associations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Ronen Barzel
|
@@ -10,118 +9,104 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date: 2013-
|
12
|
+
date: 2013-05-30 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: schema_plus
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
|
-
- -
|
18
|
+
- - '>='
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: '0'
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
|
-
- -
|
25
|
+
- - '>='
|
29
26
|
- !ruby/object:Gem::Version
|
30
27
|
version: '0'
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: rake
|
33
30
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
31
|
requirements:
|
36
|
-
- -
|
32
|
+
- - '>='
|
37
33
|
- !ruby/object:Gem::Version
|
38
34
|
version: '0'
|
39
35
|
type: :development
|
40
36
|
prerelease: false
|
41
37
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
38
|
requirements:
|
44
|
-
- -
|
39
|
+
- - '>='
|
45
40
|
- !ruby/object:Gem::Version
|
46
41
|
version: '0'
|
47
42
|
- !ruby/object:Gem::Dependency
|
48
43
|
name: rdoc
|
49
44
|
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
45
|
requirements:
|
52
|
-
- -
|
46
|
+
- - '>='
|
53
47
|
- !ruby/object:Gem::Version
|
54
48
|
version: '0'
|
55
49
|
type: :development
|
56
50
|
prerelease: false
|
57
51
|
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
52
|
requirements:
|
60
|
-
- -
|
53
|
+
- - '>='
|
61
54
|
- !ruby/object:Gem::Version
|
62
55
|
version: '0'
|
63
56
|
- !ruby/object:Gem::Dependency
|
64
57
|
name: rspec
|
65
58
|
requirement: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
59
|
requirements:
|
68
|
-
- -
|
60
|
+
- - '>='
|
69
61
|
- !ruby/object:Gem::Version
|
70
62
|
version: '0'
|
71
63
|
type: :development
|
72
64
|
prerelease: false
|
73
65
|
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
66
|
requirements:
|
76
|
-
- -
|
67
|
+
- - '>='
|
77
68
|
- !ruby/object:Gem::Version
|
78
69
|
version: '0'
|
79
70
|
- !ruby/object:Gem::Dependency
|
80
71
|
name: sqlite3
|
81
72
|
requirement: !ruby/object:Gem::Requirement
|
82
|
-
none: false
|
83
73
|
requirements:
|
84
|
-
- -
|
74
|
+
- - '>='
|
85
75
|
- !ruby/object:Gem::Version
|
86
76
|
version: '0'
|
87
77
|
type: :development
|
88
78
|
prerelease: false
|
89
79
|
version_requirements: !ruby/object:Gem::Requirement
|
90
|
-
none: false
|
91
80
|
requirements:
|
92
|
-
- -
|
81
|
+
- - '>='
|
93
82
|
- !ruby/object:Gem::Version
|
94
83
|
version: '0'
|
95
84
|
- !ruby/object:Gem::Dependency
|
96
85
|
name: simplecov
|
97
86
|
requirement: !ruby/object:Gem::Requirement
|
98
|
-
none: false
|
99
87
|
requirements:
|
100
|
-
- -
|
88
|
+
- - '>='
|
101
89
|
- !ruby/object:Gem::Version
|
102
90
|
version: '0'
|
103
91
|
type: :development
|
104
92
|
prerelease: false
|
105
93
|
version_requirements: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
94
|
requirements:
|
108
|
-
- -
|
95
|
+
- - '>='
|
109
96
|
- !ruby/object:Gem::Version
|
110
97
|
version: '0'
|
111
98
|
- !ruby/object:Gem::Dependency
|
112
99
|
name: simplecov-gem-adapter
|
113
100
|
requirement: !ruby/object:Gem::Requirement
|
114
|
-
none: false
|
115
101
|
requirements:
|
116
|
-
- -
|
102
|
+
- - '>='
|
117
103
|
- !ruby/object:Gem::Version
|
118
104
|
version: '0'
|
119
105
|
type: :development
|
120
106
|
prerelease: false
|
121
107
|
version_requirements: !ruby/object:Gem::Requirement
|
122
|
-
none: false
|
123
108
|
requirements:
|
124
|
-
- -
|
109
|
+
- - '>='
|
125
110
|
- !ruby/object:Gem::Version
|
126
111
|
version: '0'
|
127
112
|
description: SchemaAssociations extends ActiveRecord to automatically create associations
|
@@ -139,7 +124,7 @@ files:
|
|
139
124
|
- .gitignore
|
140
125
|
- .travis.yml
|
141
126
|
- MIT-LICENSE
|
142
|
-
- README.
|
127
|
+
- README.md
|
143
128
|
- Rakefile
|
144
129
|
- gemfiles/Gemfile.rails-3.2
|
145
130
|
- gemfiles/Gemfile.rails-3.2.lock
|
@@ -155,27 +140,26 @@ files:
|
|
155
140
|
- spec/spec_helper.rb
|
156
141
|
homepage: https://github.com/lomba/schema_associations
|
157
142
|
licenses: []
|
143
|
+
metadata: {}
|
158
144
|
post_install_message:
|
159
145
|
rdoc_options: []
|
160
146
|
require_paths:
|
161
147
|
- lib
|
162
148
|
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
-
none: false
|
164
149
|
requirements:
|
165
|
-
- -
|
150
|
+
- - '>='
|
166
151
|
- !ruby/object:Gem::Version
|
167
152
|
version: '0'
|
168
153
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
-
none: false
|
170
154
|
requirements:
|
171
|
-
- -
|
155
|
+
- - '>='
|
172
156
|
- !ruby/object:Gem::Version
|
173
157
|
version: '0'
|
174
158
|
requirements: []
|
175
159
|
rubyforge_project: schema_associations
|
176
|
-
rubygems_version:
|
160
|
+
rubygems_version: 2.0.3
|
177
161
|
signing_key:
|
178
|
-
specification_version:
|
162
|
+
specification_version: 4
|
179
163
|
summary: ActiveRecord extension that automatically (DRY) creates associations based
|
180
164
|
on the schema
|
181
165
|
test_files:
|
data/README.rdoc
DELETED
@@ -1,330 +0,0 @@
|
|
1
|
-
= SchemaAssociations
|
2
|
-
|
3
|
-
SchemaAssiciations is an ActiveRecord extension that keeps your model class
|
4
|
-
definitions simpler and more DRY, by automatically defining associations
|
5
|
-
based on the database schema.
|
6
|
-
|
7
|
-
{<img src="https://secure.travis-ci.org/lomba/schema_associations.png"/>}[http://travis-ci.org/lomba/schema_associations]
|
8
|
-
{<img src="https://gemnasium.com/lomba/schema_associations.png" alt="Dependency Status" />}[https://gemnasium.com/lomba/schema_associations]
|
9
|
-
|
10
|
-
== Overview
|
11
|
-
|
12
|
-
One of the great things about Rails (ActiveRecord, in particular) is that
|
13
|
-
it inspects the database and automatically defines accessors for all your
|
14
|
-
columns, keeping your model class definitions simple and DRY. That's great
|
15
|
-
for simple data columns, but where it falls down is when your table
|
16
|
-
contains references to other tables: then the "accessors" you need are the
|
17
|
-
associations defined using +belongs_to+, +has_one+, +has_many+, and
|
18
|
-
+has_and_belongs_to_many+ -- and you need to put them into your model class
|
19
|
-
definitions by hand. In fact, for every relation, you need to define two
|
20
|
-
associations each listing its inverse, such as
|
21
|
-
|
22
|
-
class Post < ActiveRecord::Base
|
23
|
-
has_many :comments, :inverse_of => :post
|
24
|
-
end
|
25
|
-
|
26
|
-
class Comment < ActiveReocrd::Base
|
27
|
-
belongs_to :post, :inverse_of => :comments
|
28
|
-
end
|
29
|
-
|
30
|
-
....which isn't so DRY.
|
31
|
-
|
32
|
-
Enter the SchemaAssociations gem. It extends ActiveRecord to automatically
|
33
|
-
define the appropriate associations based on foreign key constraints in the
|
34
|
-
database. SchemaAssociations builds on the
|
35
|
-
{+schema_plus+}[http://rubygems.org/gems/schema_plus] gem that
|
36
|
-
automatically defines foreign key constraints. So the common case is simple -- if you have this in your migration:
|
37
|
-
|
38
|
-
create_table :posts do |t|
|
39
|
-
end
|
40
|
-
|
41
|
-
create_table :comments do |t|
|
42
|
-
t.integer post_id
|
43
|
-
end
|
44
|
-
|
45
|
-
Then all you need for your models is:
|
46
|
-
|
47
|
-
class Post < ActiveRecord::Base
|
48
|
-
end
|
49
|
-
|
50
|
-
class Comment < ActiveRecord::Base
|
51
|
-
end
|
52
|
-
|
53
|
-
and SchemaAssociations defines the appropriate associations under the hood.
|
54
|
-
|
55
|
-
=== What if I want something special?
|
56
|
-
|
57
|
-
You're always free to define associations yourself, if for example you want
|
58
|
-
to pass special options. SchemaAssociations won't clobber any existing
|
59
|
-
definitions.
|
60
|
-
|
61
|
-
You can also control the behavior with various options, globally via
|
62
|
-
SchemaAssociations::setup or per-model via SchemaAssociations::ActiveRecord#schema_associations, such as:
|
63
|
-
|
64
|
-
class Post < ActiveRecord::Base
|
65
|
-
schema_associations :concise_names => false
|
66
|
-
end
|
67
|
-
|
68
|
-
See SchemaAssociations::Config for the available options.
|
69
|
-
|
70
|
-
=== This seems cool, but I'm worried about too much automagic
|
71
|
-
|
72
|
-
You can globally turn off automatic creation in <tt>config/initializers/schema_associations.rb</tt>:
|
73
|
-
|
74
|
-
SchemaAssociations.setup do |config|
|
75
|
-
config.auto_create = false
|
76
|
-
end
|
77
|
-
|
78
|
-
Then in any model where you want automatic associations, just do
|
79
|
-
|
80
|
-
class Post < ActiveRecord::Base
|
81
|
-
schema_associations
|
82
|
-
end
|
83
|
-
|
84
|
-
You can also pass options as per above.
|
85
|
-
|
86
|
-
== Full Details
|
87
|
-
|
88
|
-
=== The basics
|
89
|
-
|
90
|
-
The common cases work entirely as you'd expect. For a one-to-many
|
91
|
-
relationship using standard naming conventions:
|
92
|
-
|
93
|
-
# migration:
|
94
|
-
|
95
|
-
create_table :comments do |t|
|
96
|
-
t.integer post_id
|
97
|
-
end
|
98
|
-
|
99
|
-
# schema_associations defines:
|
100
|
-
|
101
|
-
class Post < ActiveRecord::Base
|
102
|
-
has_many :comments
|
103
|
-
end
|
104
|
-
|
105
|
-
class Comment < ActiveReocrd::Base
|
106
|
-
belongs_to :post
|
107
|
-
end
|
108
|
-
|
109
|
-
For a one-to-one relationship:
|
110
|
-
|
111
|
-
# migration:
|
112
|
-
|
113
|
-
create_table :comments do |t|
|
114
|
-
t.integer post_id, :index => :unique # (using the :index option provided by schema_plus )
|
115
|
-
end
|
116
|
-
|
117
|
-
# schema_associations defines:
|
118
|
-
|
119
|
-
class Post < ActiveRecord::Base
|
120
|
-
has_one :comment
|
121
|
-
end
|
122
|
-
|
123
|
-
class Comment < ActiveReocrd::Base
|
124
|
-
belongs_to :post
|
125
|
-
end
|
126
|
-
|
127
|
-
And for many-to-many relationships:
|
128
|
-
|
129
|
-
# migration:
|
130
|
-
|
131
|
-
create_table :groups_members do |t|
|
132
|
-
integer :group_id
|
133
|
-
integer :member_id
|
134
|
-
end
|
135
|
-
|
136
|
-
# schema_associations defines:
|
137
|
-
|
138
|
-
class Group < ActiveReocrd::Base
|
139
|
-
has_and_belongs_to_many :members
|
140
|
-
end
|
141
|
-
|
142
|
-
class Member < ActiveRecord::Base
|
143
|
-
has_and_belongs_to_many :groups
|
144
|
-
end
|
145
|
-
|
146
|
-
=== Unusual names, multiple references
|
147
|
-
|
148
|
-
Sometimes you want or need to deviate from the simple naming conventions. In
|
149
|
-
this case, the +belongs_to+ relationship name is taken from the name of the
|
150
|
-
foreign key column, and the +has_many+ or +has_one+ is named by the referencing
|
151
|
-
table, suffixed with "as" the relationship name. An example should make this clear...
|
152
|
-
|
153
|
-
Suppose your company hires interns, and each intern is assigned a manager and a
|
154
|
-
mentor, who are regular employees.
|
155
|
-
|
156
|
-
create_table :interns do |t|
|
157
|
-
t.integer :manager_id, :references => :employees
|
158
|
-
t.integer :mentor_id, :references => :employees
|
159
|
-
end
|
160
|
-
|
161
|
-
SchemaAssociations defines a +belongs_to+ association for each reference, named according to
|
162
|
-
the column:
|
163
|
-
|
164
|
-
class Intern < ActiveRecord::Base
|
165
|
-
belongs_to :manager, :class_name => "Employee", :foreign_key => "manager_id"
|
166
|
-
belongs_to :mentor, :class_name => "Employee", :foreign_key => "mentor_id"
|
167
|
-
end
|
168
|
-
|
169
|
-
And the corresponding +has_many+ association each gets a suffix to indicate which one relation it refers to:
|
170
|
-
|
171
|
-
class Employee < ActiveRecord::Base
|
172
|
-
has_many :interns_as_manager, :class_name => "Intern", :foreign_key => "manager_id"
|
173
|
-
has_many :interns_as_mentor, :class_name => "Intern", :foreign_key => "mentor_id"
|
174
|
-
end
|
175
|
-
|
176
|
-
=== Special case for trees
|
177
|
-
|
178
|
-
If your forward relation is named "parent", SchemaAssociations names the reverse
|
179
|
-
relation "child" or "children". That is, if you have:
|
180
|
-
|
181
|
-
create_table :nodes
|
182
|
-
t.integer :parent_id # schema_plus assumes it's a reference to this table
|
183
|
-
end
|
184
|
-
|
185
|
-
Then SchemaAssociations will define
|
186
|
-
|
187
|
-
class Node < ActiveRecord::Base
|
188
|
-
belongs_to :parent, :class_name => "Node", :foreign_key => "parent_id"
|
189
|
-
has_many :children, :class_name => "Node", :foreign_key => "parent_id"
|
190
|
-
end
|
191
|
-
|
192
|
-
=== Concise names
|
193
|
-
|
194
|
-
For modularity in your tables and classes, you might use a common prefix for
|
195
|
-
related objects. For example, you may have widgets each of which has a color,
|
196
|
-
and might have one base that has a top color and a bottom color, from the same
|
197
|
-
set of colors.
|
198
|
-
|
199
|
-
create_table :widget_colors |t|
|
200
|
-
end
|
201
|
-
|
202
|
-
create_table :widgets do |t|
|
203
|
-
t.integer :widget_color_id
|
204
|
-
end
|
205
|
-
|
206
|
-
create_table :widget_base
|
207
|
-
t.integer :widget_id, :index => :unique
|
208
|
-
t.integer :top_widget_color_id, :references => :widget_colors
|
209
|
-
t.integer :bottom_widget_color_id, :references => :widget_colors
|
210
|
-
end
|
211
|
-
|
212
|
-
Using the full name for the associations would make your code verbose and not quite DRY:
|
213
|
-
|
214
|
-
@widget.widget_color
|
215
|
-
@widget.widget_base.top_widget_color
|
216
|
-
|
217
|
-
Instead, by default, SchemaAssociations uses concise names: shared leading
|
218
|
-
words are removed from the association name. So instead of the above, your
|
219
|
-
code looks like:
|
220
|
-
|
221
|
-
@widget.color
|
222
|
-
@widget.base.top_color
|
223
|
-
|
224
|
-
i.e. these associations would be defined:
|
225
|
-
|
226
|
-
class WidgetColor < ActiveRecord::Base
|
227
|
-
has_many :widgets, :class_name => "Widget", :foreign_key => "widget_color_id"
|
228
|
-
has_many :bases_as_top, :class_name => "WidgetBase", :foreign_key => "top_widget_color_id"
|
229
|
-
has_many :bases_as_bottom, :class_name => "WidgetBase", :foreign_key => "bottom_widget_color_id"
|
230
|
-
end
|
231
|
-
|
232
|
-
class Widget < ActiveRecord::Base
|
233
|
-
belongs_to :color, :class_name => "WidgetColor", :foreign_key => "widget_color_id"
|
234
|
-
has_one :base, :class_name => "WidgetBase", :foreign_key => "widget_base_id"
|
235
|
-
end
|
236
|
-
|
237
|
-
class WidgetBase < ActiveRecord::Base
|
238
|
-
belongs_to :top_color, :class_name => "WidgetColor", :foreign_key => "top_widget_color_id"
|
239
|
-
belongs_to :bottom_color, :class_name => "WidgetColor", :foreign_key => "bottom_widget_color_id"
|
240
|
-
belongs_to :widget, :class_name => "Widget", :foreign_key => "widget_id"
|
241
|
-
end
|
242
|
-
|
243
|
-
If you like the formality of using full names for the asociations, you can turn off concise names globally or per-model, see SchemaAssociations::Config
|
244
|
-
|
245
|
-
=== Ordering +has_many+ using +position+
|
246
|
-
|
247
|
-
If the target of a +has_many+ association has a column named +position+, SchemaAssociation will specify <tt>:order => :position</tt> for the association. That is,
|
248
|
-
|
249
|
-
create_table :comments do |t|
|
250
|
-
t.integer post_id
|
251
|
-
t.integer position
|
252
|
-
end
|
253
|
-
|
254
|
-
leads to
|
255
|
-
|
256
|
-
class Post < ActiveRecord::Base
|
257
|
-
has_many :comments, :order => :position
|
258
|
-
end
|
259
|
-
|
260
|
-
|
261
|
-
== How do I know what it did?
|
262
|
-
|
263
|
-
If you're curious (or dubious) about what associations SchemaAssociations defines, you can check the log file. For every assocation that SchemaAssociations defines, it generates an info entry such as
|
264
|
-
|
265
|
-
[schema_associations] Post.has_many :comments, :class_name "Comment", :foreign_key "comment_id"
|
266
|
-
|
267
|
-
which shows the exact method definition call.
|
268
|
-
|
269
|
-
SchemaAssociations defines the associations lazily, only creating them when
|
270
|
-
they're first needed. So you may need to search through the log file to find
|
271
|
-
them all (and some may not be defined at all if they were never needed for the
|
272
|
-
use cases that you logged).
|
273
|
-
|
274
|
-
== Compatibility
|
275
|
-
|
276
|
-
SchemaAssociations supports all combinations of:
|
277
|
-
* rails 3.2
|
278
|
-
* MRI ruby 1.9.2 or 1.9.3
|
279
|
-
|
280
|
-
Note: As of version 1.0.0, ruby 1.8.7 and rails < 3.2 are no longer supported. The last version to support them is 0.1.2
|
281
|
-
|
282
|
-
== Installation
|
283
|
-
|
284
|
-
Install from http://rubygems.org via
|
285
|
-
|
286
|
-
$ gem install "schema_associations"
|
287
|
-
|
288
|
-
or in a Gemfile
|
289
|
-
|
290
|
-
gem "schema_associations"
|
291
|
-
|
292
|
-
== Testing
|
293
|
-
|
294
|
-
SchemaAssociations is tested using rspec, sqlite3, and rvm, with some
|
295
|
-
hackery to test against multiple versions of rails and ruby. To run the
|
296
|
-
full combo of tests, after you've forked & cloned:
|
297
|
-
|
298
|
-
$ cd schema_associations
|
299
|
-
$ ./runspecs --install # do this once to install gem dependencies for all versions (slow)
|
300
|
-
$ ./runspecs # as many times as you like
|
301
|
-
|
302
|
-
See <tt>./runspecs --help</tt> for more options. In particular, to run
|
303
|
-
rspec on a specific file or example (rather than running the full suite)
|
304
|
-
you can do, e.g.
|
305
|
-
|
306
|
-
$ ./runspecs [other options] --rspec -- spec/association_spec.rb -e 'base'
|
307
|
-
|
308
|
-
If you're running ruby 1.9, code coverage results will be in coverage/index.html -- it should be at 100% coverage.
|
309
|
-
|
310
|
-
== Release notes:
|
311
|
-
|
312
|
-
=== 1.0.1
|
313
|
-
|
314
|
-
* Bug fix: use singular :inverse_of for :belongs_to of a :has_one
|
315
|
-
|
316
|
-
=== 1.0.0
|
317
|
-
|
318
|
-
* Use :inverse_of in generated associations
|
319
|
-
|
320
|
-
* Drop support for ruby 1.8.7 and rails < 3.2
|
321
|
-
|
322
|
-
== History
|
323
|
-
|
324
|
-
* SchemaAssociations is derived from the "Red Hill On Rails" plugin foreign_key_associations originally created by harukizaemon (https://github.com/harukizaemon)
|
325
|
-
|
326
|
-
* SchemaAssociations was created in 2011 by Michal Lomnicki and Ronen Barzel
|
327
|
-
|
328
|
-
== License
|
329
|
-
|
330
|
-
This gem is released under the MIT license.
|