schema_associations 1.2.4 → 1.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +259 -160
- data/lib/schema_associations.rb +3 -10
- data/lib/schema_associations/active_record/associations.rb +201 -208
- data/lib/schema_associations/version.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- metadata +2 -3
- data/lib/schema_associations/railtie.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc1905f1df57594ba2e456f9453099827e62df17
|
4
|
+
data.tar.gz: f85030cb99925c5589c9499cbde3f21d177b01fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d747c0d7bb98aff9f230d91ec4c67d6f78d6b6d7ab75473d6486f5a8dfca9b839c949c8b3b34f41b7e0a0c42be2809cbf9f748ff47e6c7a9028262718284e15e
|
7
|
+
data.tar.gz: 8335b7d788b8fdc5f35fec400892ce3850c5227038d7a9ade9faa872c8fee03dcd71c3af340d097bd75d6fc9321fc282616accfec189ed31352fb41929922a8f
|
data/README.md
CHANGED
@@ -7,7 +7,6 @@ on the database schema.
|
|
7
7
|
[![Gem Version](https://badge.fury.io/rb/schema_associations.svg)](http://badge.fury.io/rb/schema_associations)
|
8
8
|
[![Build Status](https://secure.travis-ci.org/SchemaPlus/schema_associations.svg)](http://travis-ci.org/SchemaPlus/schema_associations)
|
9
9
|
[![Coverage Status](https://img.shields.io/coveralls/SchemaPlus/schema_associations.svg)](https://coveralls.io/r/SchemaPlus/schema_associations?branch=master)
|
10
|
-
[![Dependency Status](https://gemnasium.com/lomba/schema_associations.svg)](https://gemnasium.com/lomba/schema_associations)
|
11
10
|
|
12
11
|
|
13
12
|
## Overview
|
@@ -22,37 +21,42 @@ defined using `belongs_to`, `has_one`, `has_many`, and
|
|
22
21
|
definitions by hand. In fact, for every relation, you need to define two
|
23
22
|
associations each listing its inverse, such as
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
```ruby
|
25
|
+
class Post < ActiveRecord::Base
|
26
|
+
has_many :comments, inverse_of: :post
|
27
|
+
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
class Comment < ActiveRecord::Base
|
30
|
+
belongs_to :post, inverse_of: :comments
|
31
|
+
end
|
32
|
+
```
|
32
33
|
|
33
34
|
....which isn't so DRY.
|
34
35
|
|
35
|
-
Enter the SchemaAssociations gem. It extends ActiveRecord to automatically
|
36
|
-
define the appropriate associations based on foreign key constraints in the
|
37
|
-
database. SchemaAssociations builds on the
|
38
|
-
[schema_plus](http://rubygems.org/gems/schema_plus) gem that automatically
|
39
|
-
defines foreign key constraints. So the common case is simple -- if you have
|
40
|
-
this in your migration:
|
36
|
+
Enter the SchemaAssociations gem. It extends ActiveRecord to automatically define the appropriate associations based on foreign key constraints in the database.
|
41
37
|
|
42
|
-
|
43
|
-
|
38
|
+
SchemaAssociations works particularly well with the
|
39
|
+
[schema_auto_foreign_keys](http://github.com/SchemaPlus/schema_auto_foreign_keys) gem which automatically
|
40
|
+
defines foreign key constraints. So the common case is simple -- if you have this in your migration:
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
```ruby
|
43
|
+
create_table :posts do |t|
|
44
|
+
end
|
45
|
+
|
46
|
+
create_table :comments do |t|
|
47
|
+
t.integer post_id
|
48
|
+
end
|
49
|
+
```
|
48
50
|
|
49
51
|
Then all you need for your models is:
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
+
```ruby
|
54
|
+
class Post < ActiveRecord::Base
|
55
|
+
end
|
53
56
|
|
54
|
-
|
55
|
-
|
57
|
+
class Comment < ActiveRecord::Base
|
58
|
+
end
|
59
|
+
```
|
56
60
|
|
57
61
|
and SchemaAssociations defines the appropriate associations under the hood.
|
58
62
|
|
@@ -60,34 +64,30 @@ and SchemaAssociations defines the appropriate associations under the hood.
|
|
60
64
|
|
61
65
|
You're always free to define associations yourself, if for example you want to
|
62
66
|
pass special options. SchemaAssociations won't clobber any existing
|
63
|
-
definitions.
|
64
|
-
|
65
|
-
You can also control the behavior with various options, globally via
|
66
|
-
SchemaAssociations::setup or per-model via
|
67
|
-
SchemaAssociations::ActiveRecord#schema_associations, such as:
|
68
|
-
|
69
|
-
class Post < ActiveRecord::Base
|
70
|
-
schema_associations :concise_names => false
|
71
|
-
end
|
67
|
+
definitions.
|
72
68
|
|
73
|
-
See the [
|
69
|
+
You can also control the behavior with various options, via a global initializer and/or per-model. See the [Configuration section](#configuration) for the available options.
|
74
70
|
|
75
71
|
### This seems cool, but I'm worried about too much automagic
|
76
72
|
|
77
73
|
You can globally turn off automatic creation in
|
78
74
|
`config/initializers/schema_associations.rb`:
|
79
75
|
|
80
|
-
|
81
|
-
|
82
|
-
|
76
|
+
```ruby
|
77
|
+
SchemaAssociations.setup do |config|
|
78
|
+
config.auto_create = false
|
79
|
+
end
|
80
|
+
```
|
83
81
|
|
84
82
|
Then in any model where you want automatic associations, just do
|
85
83
|
|
86
|
-
|
87
|
-
|
88
|
-
|
84
|
+
```ruby
|
85
|
+
class Post < ActiveRecord::Base
|
86
|
+
schema_associations
|
87
|
+
end
|
88
|
+
```
|
89
89
|
|
90
|
-
You can also pass options as
|
90
|
+
You can also pass options as described in [Configurtion](#configuration)
|
91
91
|
|
92
92
|
## Full Details
|
93
93
|
|
@@ -96,58 +96,70 @@ You can also pass options as per above.
|
|
96
96
|
The common cases work entirely as you'd expect. For a one-to-many
|
97
97
|
relationship using standard naming conventions:
|
98
98
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
99
|
+
```ruby
|
100
|
+
#
|
101
|
+
# migration:
|
102
|
+
#
|
103
|
+
create_table :comments do |t|
|
104
|
+
t.integer post_id
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# schema_associations defines:
|
109
|
+
#
|
110
|
+
class Post < ActiveRecord::Base
|
111
|
+
has_many :comments
|
112
|
+
end
|
113
|
+
|
114
|
+
class Comment < ActiveReocrd::Base
|
115
|
+
belongs_to :post
|
116
|
+
end
|
117
|
+
```
|
114
118
|
|
115
119
|
For a one-to-one relationship:
|
116
120
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
121
|
+
```ruby
|
122
|
+
#
|
123
|
+
# migration:
|
124
|
+
#
|
125
|
+
create_table :comments do |t|
|
126
|
+
t.integer post_id, index: :unique # (using the :index option provided by schema_plus )
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# schema_associations defines:
|
131
|
+
#
|
132
|
+
class Post < ActiveRecord::Base
|
133
|
+
has_one :comment
|
134
|
+
end
|
135
|
+
|
136
|
+
class Comment < ActiveReocrd::Base
|
137
|
+
belongs_to :post
|
138
|
+
end
|
139
|
+
```
|
132
140
|
|
133
141
|
And for many-to-many relationships:
|
134
142
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
143
|
+
```ruby
|
144
|
+
#
|
145
|
+
# migration:
|
146
|
+
#
|
147
|
+
create_table :groups_members do |t|
|
148
|
+
integer :group_id
|
149
|
+
integer :member_id
|
150
|
+
end
|
151
|
+
|
152
|
+
#
|
153
|
+
# schema_associations defines:
|
154
|
+
#
|
155
|
+
class Group < ActiveReocrd::Base
|
156
|
+
has_and_belongs_to_many :members
|
157
|
+
end
|
158
|
+
|
159
|
+
class Member < ActiveRecord::Base
|
160
|
+
has_and_belongs_to_many :groups
|
161
|
+
end
|
162
|
+
```
|
151
163
|
|
152
164
|
### Unusual names, multiple references
|
153
165
|
|
@@ -160,139 +172,167 @@ should make this clear...
|
|
160
172
|
Suppose your company hires interns, and each intern is assigned a manager and
|
161
173
|
a mentor, who are regular employees.
|
162
174
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
175
|
+
```ruby
|
176
|
+
create_table :interns do |t|
|
177
|
+
t.integer :manager_id, references: :employees
|
178
|
+
t.integer :mentor_id, references: :employees
|
179
|
+
end
|
180
|
+
```
|
167
181
|
|
168
182
|
SchemaAssociations defines a `belongs_to` association for each reference,
|
169
183
|
named according to the column:
|
170
184
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
185
|
+
```ruby
|
186
|
+
class Intern < ActiveRecord::Base
|
187
|
+
belongs_to :manager, class_name: "Employee", foreign_key: "manager_id"
|
188
|
+
belongs_to :mentor, class_name: "Employee", foreign_key: "mentor_id"
|
189
|
+
end
|
190
|
+
```
|
175
191
|
|
176
192
|
And the corresponding `has_many` association each gets a suffix to indicate
|
177
193
|
which one relation it refers to:
|
178
194
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
195
|
+
```ruby
|
196
|
+
class Employee < ActiveRecord::Base
|
197
|
+
has_many :interns_as_manager, class_name: "Intern", foreign_key: "manager_id"
|
198
|
+
has_many :interns_as_mentor, class_name: "Intern", foreign_key: "mentor_id"
|
199
|
+
end
|
200
|
+
```
|
183
201
|
|
184
202
|
### Special case for trees
|
185
203
|
|
186
204
|
If your forward relation is named "parent", SchemaAssociations names the
|
187
205
|
reverse relation "child" or "children". That is, if you have:
|
188
206
|
|
189
|
-
|
190
|
-
|
191
|
-
|
207
|
+
```ruby
|
208
|
+
create_table :nodes
|
209
|
+
t.integer :parent_id # schema_plus assumes it's a reference to this table
|
210
|
+
end
|
211
|
+
```
|
192
212
|
|
193
213
|
Then SchemaAssociations will define
|
194
214
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
215
|
+
```ruby
|
216
|
+
class Node < ActiveRecord::Base
|
217
|
+
belongs_to :parent, class_name: "Node", foreign_key: "parent_id"
|
218
|
+
has_many :children, class_name: "Node", foreign_key: "parent_id"
|
219
|
+
end
|
220
|
+
```
|
199
221
|
|
200
222
|
### Concise names
|
201
223
|
|
202
224
|
For modularity in your tables and classes, you might use a common prefix for
|
203
|
-
related objects. For example, you may have widgets each of which has a color,
|
204
|
-
and might have one base that has a top color and a bottom color, from the same
|
205
|
-
set of colors.
|
225
|
+
related objects. For example, you may have widgets each of which has a color, and each widget might have one frob that has a top color and a bottom color--all from the same set of colors.
|
206
226
|
|
207
|
-
|
208
|
-
|
227
|
+
```ruby
|
228
|
+
create_table :widget_colors |t|
|
229
|
+
end
|
209
230
|
|
210
|
-
|
211
|
-
|
212
|
-
|
231
|
+
create_table :widgets do |t|
|
232
|
+
t.integer :widget_color_id
|
233
|
+
end
|
213
234
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
235
|
+
create_table :widget_frobs
|
236
|
+
t.integer :widget_id, index: :unique
|
237
|
+
t.integer :top_widget_color_id, references: :widget_colors
|
238
|
+
t.integer :bottom_widget_color_id, references: :widget_colors
|
239
|
+
end
|
240
|
+
```
|
219
241
|
|
220
242
|
Using the full name for the associations would make your code verbose and not
|
221
243
|
quite DRY:
|
222
244
|
|
223
|
-
|
224
|
-
|
245
|
+
```ruby
|
246
|
+
@widget.widget_color
|
247
|
+
@widget.widget_frob.top_widget_color
|
248
|
+
```
|
225
249
|
|
226
250
|
Instead, by default, SchemaAssociations uses concise names: shared leading
|
227
251
|
words are removed from the association name. So instead of the above, your
|
228
252
|
code looks like:
|
229
253
|
|
230
|
-
|
231
|
-
|
254
|
+
```ruby
|
255
|
+
@widget.color
|
256
|
+
@widget.frob.top_color
|
257
|
+
```
|
232
258
|
|
233
259
|
i.e. these associations would be defined:
|
234
260
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
261
|
+
```ruby
|
262
|
+
class WidgetColor < ActiveRecord::Base
|
263
|
+
has_many :widgets, class_name: "Widget", foreign_key: "widget_color_id"
|
264
|
+
has_many :frobs_as_top, class_name: "WidgetFrob", foreign_key: "top_widget_color_id"
|
265
|
+
has_many :frobs_as_bottom, class_name: "WidgetFrob", foreign_key: "bottom_widget_color_id"
|
266
|
+
end
|
267
|
+
|
268
|
+
class Widget < ActiveRecord::Base
|
269
|
+
belongs_to :color, class_name: "WidgetColor", foreign_key: "widget_color_id"
|
270
|
+
has_one :frob, class_name: "WidgetFrob", foreign_key: "widget_frob_id"
|
271
|
+
end
|
272
|
+
|
273
|
+
class WidgetFrob < ActiveRecord::Base
|
274
|
+
belongs_to :top_color, class_name: "WidgetColor", foreign_key: "top_widget_color_id"
|
275
|
+
belongs_to :bottom_color, class_name: "WidgetColor", foreign_key: "bottom_widget_color_id"
|
276
|
+
belongs_to :widget, class_name: "Widget", foreign_key: "widget_id"
|
277
|
+
end
|
278
|
+
```
|
251
279
|
|
252
280
|
If you like the formality of using full names for the asociations, you can
|
253
|
-
turn off concise names globally or per-model, see [
|
281
|
+
turn off concise names globally or per-model, see [Configuration](#configuration).
|
254
282
|
|
255
283
|
### Ordering `has_many` using `position`
|
256
284
|
|
257
285
|
If the target of a `has_many` association has a column named `position`,
|
258
|
-
SchemaAssociations will specify
|
286
|
+
SchemaAssociations will specify `order: :position` for the association.
|
259
287
|
That is,
|
260
288
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
289
|
+
```ruby
|
290
|
+
create_table :comments do |t|
|
291
|
+
t.integer post_id
|
292
|
+
t.integer position
|
293
|
+
end
|
294
|
+
```
|
265
295
|
|
266
296
|
leads to
|
267
297
|
|
268
|
-
|
269
|
-
|
270
|
-
|
298
|
+
```ruby
|
299
|
+
class Post < ActiveRecord::Base
|
300
|
+
has_many :comments, order: :position
|
301
|
+
end
|
302
|
+
```
|
271
303
|
|
272
|
-
## Table names
|
304
|
+
## Table names, model class names, and modules
|
273
305
|
|
274
|
-
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
|
306
|
+
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 namespaced in a module.
|
275
307
|
|
276
308
|
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:
|
277
309
|
|
278
|
-
|
279
|
-
|
310
|
+
```ruby
|
311
|
+
hpy_campers
|
312
|
+
hpy_go_lucky
|
313
|
+
```
|
280
314
|
|
281
315
|
The default model class names would be
|
282
316
|
|
283
|
-
|
284
|
-
|
285
|
-
|
317
|
+
```ruby
|
318
|
+
HpyCampers
|
319
|
+
HpyGoLucky
|
320
|
+
```
|
321
|
+
|
286
322
|
But if instead you wanted
|
287
323
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
324
|
+
```ruby
|
325
|
+
Happy::Campers
|
326
|
+
Happy::GoLucky
|
327
|
+
```
|
328
|
+
|
329
|
+
you would define the mapping in the [configuration](#configuration):
|
292
330
|
|
293
|
-
|
294
|
-
|
295
|
-
|
331
|
+
```ruby
|
332
|
+
SchemaPlus.setup do |config|
|
333
|
+
config.table_prefix_map["hpy_"] = "Happy::"
|
334
|
+
end
|
335
|
+
```
|
296
336
|
|
297
337
|
Tables names that don't start with `hpy_` will continue to use the default determination.
|
298
338
|
|
@@ -317,6 +357,61 @@ they're first needed. So you may need to search through the log file to find
|
|
317
357
|
them all (and some may not be defined at all if they were never needed for the
|
318
358
|
use cases that you logged).
|
319
359
|
|
360
|
+
## Configuration
|
361
|
+
|
362
|
+
You can configure options globally in an initializer such as `config/initializers/schema_associations.rb`, e.g.
|
363
|
+
|
364
|
+
```ruby
|
365
|
+
SchemaAssociations.setup do |config|
|
366
|
+
config.concise_names = false
|
367
|
+
end
|
368
|
+
```
|
369
|
+
|
370
|
+
and/or override the options per-model, e.g.:
|
371
|
+
|
372
|
+
```ruby
|
373
|
+
class MyModel < ActiveRecord::Base
|
374
|
+
schema_associations.config concise_names: false
|
375
|
+
end
|
376
|
+
```
|
377
|
+
|
378
|
+
Here's the full list of options, with their default values:
|
379
|
+
|
380
|
+
```ruby
|
381
|
+
SchemaAssociations.setup do |config|
|
382
|
+
|
383
|
+
# Enable/disable SchemaAssociations' automatic behavior
|
384
|
+
config.auto_create = true
|
385
|
+
|
386
|
+
# Whether to use concise naming (strip out common prefixes from class names)
|
387
|
+
config.concise_names = true
|
388
|
+
|
389
|
+
# List of association names to exclude from automatic creation.
|
390
|
+
# Value is a single name, an array of names, or nil.
|
391
|
+
config.except = nil
|
392
|
+
|
393
|
+
# List of association names to include in automatic creation.
|
394
|
+
# Value is a single name, and array of names, or nil.
|
395
|
+
config.only = nil
|
396
|
+
|
397
|
+
# List of association types to exclude from automatic creation.
|
398
|
+
# Value is one or an array of :belongs_to, :has_many, :has_one, and/or
|
399
|
+
# :has_and_belongs_to_many, or nil.
|
400
|
+
config.except_type = nil
|
401
|
+
|
402
|
+
# List of association types to include in automatic creation.
|
403
|
+
# Value is one or an array of :belongs_to, :has_many, :has_one, and/or
|
404
|
+
# :has_and_belongs_to_many, or nil.
|
405
|
+
config.only_type = nil
|
406
|
+
|
407
|
+
# Hash whose keys are possible matches at the start of table names, and
|
408
|
+
# whose corresponding values are the prefix to use in front of class
|
409
|
+
# names.
|
410
|
+
config.table_prefix_map = {}
|
411
|
+
end
|
412
|
+
```
|
413
|
+
|
414
|
+
|
320
415
|
## Compatibility
|
321
416
|
|
322
417
|
SchemaAssociations is tested on all combinations of:
|
@@ -358,6 +453,10 @@ Code coverage results will be in coverage/index.html -- it should be at 100% cov
|
|
358
453
|
|
359
454
|
## Release notes:
|
360
455
|
|
456
|
+
### 1.2.5
|
457
|
+
|
458
|
+
* Use schema_monkey rather than Railties.
|
459
|
+
|
361
460
|
### 1.2.4
|
362
461
|
|
363
462
|
* Bug fix: Don't fail trying to do associations for abstract classes (mysql2 only). [#11, #12] Thanks to [@dmeranda](https://github.com/dmeranda)
|
data/lib/schema_associations.rb
CHANGED
@@ -3,7 +3,6 @@ require 'valuable'
|
|
3
3
|
|
4
4
|
require 'schema_associations/version'
|
5
5
|
require 'schema_associations/active_record/associations'
|
6
|
-
require 'schema_associations/railtie' if defined?(Rails)
|
7
6
|
|
8
7
|
module SchemaAssociations
|
9
8
|
|
@@ -17,7 +16,7 @@ module SchemaAssociations
|
|
17
16
|
# or override them per-model, e.g.:
|
18
17
|
#
|
19
18
|
# class MyModel < ActiveRecord::Base
|
20
|
-
# schema_associations
|
19
|
+
# schema_associations :concise_names => false
|
21
20
|
# end
|
22
21
|
#
|
23
22
|
class Config < Valuable
|
@@ -74,7 +73,7 @@ module SchemaAssociations
|
|
74
73
|
# names.
|
75
74
|
has_value :table_prefix_map, :default => {}
|
76
75
|
|
77
|
-
def dup
|
76
|
+
def dup # :nodoc:
|
78
77
|
self.class.new(Hash[attributes.collect{ |key, val| [key, Valuable === val ? val.class.new(val.attributes) : val] }])
|
79
78
|
end
|
80
79
|
|
@@ -108,12 +107,6 @@ module SchemaAssociations
|
|
108
107
|
yield config
|
109
108
|
end
|
110
109
|
|
111
|
-
def self.insert #:nodoc:
|
112
|
-
return if @inserted
|
113
|
-
@inserted = true
|
114
|
-
::ActiveRecord::Base.extend SchemaAssociations::ActiveRecord::Associations
|
115
|
-
end
|
116
|
-
|
117
110
|
end
|
118
111
|
|
119
|
-
|
112
|
+
SchemaMonkey.register SchemaAssociations
|
@@ -2,261 +2,254 @@ require 'ostruct'
|
|
2
2
|
|
3
3
|
module SchemaAssociations
|
4
4
|
module ActiveRecord
|
5
|
-
module Associations #:nodoc:
|
6
5
|
|
7
|
-
|
8
|
-
def self.included(base)
|
9
|
-
base.alias_method_chain :initialize, :schema_associations
|
10
|
-
end
|
11
|
-
|
12
|
-
def initialize_with_schema_associations(klass, *args)
|
13
|
-
klass.send :_load_schema_associations_associations unless klass.nil?
|
14
|
-
initialize_without_schema_associations(klass, *args)
|
15
|
-
end
|
16
|
-
end
|
6
|
+
module Relation
|
17
7
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
alias_method_chain :_reflect_on_association, :schema_associations if method_defined? :_reflect_on_association
|
22
|
-
alias_method_chain :reflect_on_all_associations, :schema_associations
|
23
|
-
end
|
24
|
-
::ActiveRecord::Relation.send :include, Relation if defined? ::ActiveRecord::Relation
|
8
|
+
def initialize(klass, *args)
|
9
|
+
klass.send :_load_schema_associations_associations unless klass.nil?
|
10
|
+
super
|
25
11
|
end
|
12
|
+
end
|
26
13
|
|
27
|
-
|
28
|
-
_load_schema_associations_associations
|
29
|
-
reflect_on_association_without_schema_associations(*args)
|
30
|
-
end
|
14
|
+
module Base
|
31
15
|
|
32
|
-
|
33
|
-
def _reflect_on_association_with_schema_associations(*args) #:nodoc:
|
34
|
-
_load_schema_associations_associations
|
35
|
-
_reflect_on_association_without_schema_associations(*args)
|
36
|
-
end
|
16
|
+
module ClassMethods
|
37
17
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
18
|
+
def reflect_on_association(*args)
|
19
|
+
_load_schema_associations_associations
|
20
|
+
super
|
21
|
+
end
|
42
22
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
23
|
+
# introduced in rails 4.1
|
24
|
+
def _reflect_on_association(*args)
|
25
|
+
_load_schema_associations_associations
|
26
|
+
super
|
27
|
+
end
|
47
28
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
#
|
53
|
-
# If <tt>:auto_create</tt> is not specified, it is implicitly
|
54
|
-
# specified as true. This allows the "non-invasive" style of using
|
55
|
-
# SchemaAssociations in which you set the global Config to
|
56
|
-
# <tt>auto_create = false</tt>, then in any model that you want auto
|
57
|
-
# associations you simply do:
|
58
|
-
#
|
59
|
-
# class MyModel < ActiveRecord::Base
|
60
|
-
# schema_associations
|
61
|
-
# end
|
62
|
-
#
|
63
|
-
# Of course other options can be passed, such as
|
64
|
-
#
|
65
|
-
# class MyModel < ActiveRecord::Base
|
66
|
-
# schema_associations :concise_names => false, :except_type => :has_and_belongs_to_many
|
67
|
-
# end
|
68
|
-
#
|
69
|
-
#
|
70
|
-
def schema_associations(opts={})
|
71
|
-
@schema_associations_config = SchemaAssociations.config.merge({:auto_create => true}.merge(opts))
|
72
|
-
end
|
29
|
+
def reflect_on_all_associations(*args)
|
30
|
+
_load_schema_associations_associations
|
31
|
+
super
|
32
|
+
end
|
73
33
|
|
74
|
-
|
75
|
-
|
76
|
-
|
34
|
+
def define_attribute_methods(*args)
|
35
|
+
super
|
36
|
+
_load_schema_associations_associations
|
37
|
+
end
|
77
38
|
|
78
|
-
|
39
|
+
# Per-model override of Config options. Use via, e.g.
|
40
|
+
# class MyModel < ActiveRecord::Base
|
41
|
+
# schema_associations :auto_create => false
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# If <tt>:auto_create</tt> is not specified, it is implicitly
|
45
|
+
# specified as true. This allows the "non-invasive" style of using
|
46
|
+
# SchemaAssociations in which you set the global Config to
|
47
|
+
# <tt>auto_create = false</tt>, then in any model that you want auto
|
48
|
+
# associations you simply do:
|
49
|
+
#
|
50
|
+
# class MyModel < ActiveRecord::Base
|
51
|
+
# schema_associations
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# Of course other options can be passed, such as
|
55
|
+
#
|
56
|
+
# class MyModel < ActiveRecord::Base
|
57
|
+
# schema_associations :concise_names => false, :except_type => :has_and_belongs_to_many
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
#
|
61
|
+
def schema_associations(opts={})
|
62
|
+
@schema_associations_config = SchemaAssociations.config.merge({:auto_create => true}.merge(opts))
|
63
|
+
end
|
79
64
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
return if abstract_class?
|
84
|
-
return unless schema_associations_config.auto_create?
|
65
|
+
def schema_associations_config # :nodoc:
|
66
|
+
@schema_associations_config ||= SchemaAssociations.config.dup
|
67
|
+
end
|
85
68
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
69
|
+
private
|
70
|
+
|
71
|
+
def _load_schema_associations_associations
|
72
|
+
return if @schema_associations_associations_loaded
|
73
|
+
@schema_associations_associations_loaded = true
|
74
|
+
return if abstract_class?
|
75
|
+
return unless schema_associations_config.auto_create?
|
76
|
+
|
77
|
+
reverse_foreign_keys.each do | foreign_key |
|
78
|
+
if foreign_key.from_table =~ /^#{table_name}_(.*)$/ || foreign_key.from_table =~ /^(.*)_#{table_name}$/
|
79
|
+
other_table = $1
|
80
|
+
if other_table == other_table.pluralize and connection.columns(foreign_key.from_table).any?{|col| col.name == "#{other_table.singularize}_id"}
|
81
|
+
_define_association(:has_and_belongs_to_many, foreign_key, other_table)
|
82
|
+
else
|
83
|
+
_define_association(:has_one_or_many, foreign_key)
|
84
|
+
end
|
91
85
|
else
|
92
86
|
_define_association(:has_one_or_many, foreign_key)
|
93
87
|
end
|
94
|
-
else
|
95
|
-
_define_association(:has_one_or_many, foreign_key)
|
96
88
|
end
|
97
|
-
end
|
98
89
|
|
99
|
-
|
100
|
-
|
90
|
+
foreign_keys.each do | foreign_key |
|
91
|
+
_define_association(:belongs_to, foreign_key)
|
92
|
+
end
|
101
93
|
end
|
102
|
-
end
|
103
94
|
|
104
|
-
|
105
|
-
|
106
|
-
|
95
|
+
def _define_association(macro, fk, referencing_table_name = nil)
|
96
|
+
column_names = Array.wrap(fk.column)
|
97
|
+
return unless column_names.size == 1
|
107
98
|
|
108
|
-
|
109
|
-
|
99
|
+
referencing_table_name ||= fk.from_table
|
100
|
+
column_name = column_names.first
|
110
101
|
|
111
|
-
|
112
|
-
|
102
|
+
references_name = fk.to_table.singularize
|
103
|
+
referencing_name = referencing_table_name.singularize
|
113
104
|
|
114
|
-
|
115
|
-
|
105
|
+
referencing_class_name = _get_class_name(referencing_name)
|
106
|
+
references_class_name = _get_class_name(references_name)
|
116
107
|
|
117
|
-
|
108
|
+
names = _determine_association_names(column_name.sub(/_id$/, ''), referencing_name, references_name)
|
118
109
|
|
119
|
-
|
110
|
+
argstr = ""
|
120
111
|
|
121
112
|
|
122
|
-
|
123
|
-
|
124
|
-
name = names[:has_many]
|
125
|
-
opts = {:class_name => referencing_class_name, :join_table => fk.from_table, :foreign_key => column_name}
|
126
|
-
when :belongs_to
|
127
|
-
name = names[:belongs_to]
|
128
|
-
opts = {:class_name => references_class_name, :foreign_key => column_name}
|
129
|
-
if connection.indexes(referencing_table_name).any?{|index| index.unique && index.columns == [column_name]}
|
130
|
-
opts[:inverse_of] = names[:has_one]
|
131
|
-
else
|
132
|
-
opts[:inverse_of] = names[:has_many]
|
133
|
-
end
|
134
|
-
|
135
|
-
when :has_one_or_many
|
136
|
-
opts = {:class_name => referencing_class_name, :foreign_key => column_name, :inverse_of => names[:belongs_to]}
|
137
|
-
# use connection.indexes and connection.colums rather than class
|
138
|
-
# methods of the referencing class because using the class
|
139
|
-
# methods would require getting the class -- which might trigger
|
140
|
-
# an autoload which could start some recursion making things much
|
141
|
-
# harder to debug.
|
142
|
-
if connection.indexes(referencing_table_name).any?{|index| index.unique && index.columns == [column_name]}
|
143
|
-
macro = :has_one
|
144
|
-
name = names[:has_one]
|
145
|
-
else
|
146
|
-
macro = :has_many
|
113
|
+
case macro
|
114
|
+
when :has_and_belongs_to_many
|
147
115
|
name = names[:has_many]
|
148
|
-
|
149
|
-
|
150
|
-
|
116
|
+
opts = {:class_name => referencing_class_name, :join_table => fk.from_table, :foreign_key => column_name}
|
117
|
+
when :belongs_to
|
118
|
+
name = names[:belongs_to]
|
119
|
+
opts = {:class_name => references_class_name, :foreign_key => column_name}
|
120
|
+
if connection.indexes(referencing_table_name).any?{|index| index.unique && index.columns == [column_name]}
|
121
|
+
opts[:inverse_of] = names[:has_one]
|
122
|
+
else
|
123
|
+
opts[:inverse_of] = names[:has_many]
|
124
|
+
end
|
125
|
+
|
126
|
+
when :has_one_or_many
|
127
|
+
opts = {:class_name => referencing_class_name, :foreign_key => column_name, :inverse_of => names[:belongs_to]}
|
128
|
+
# use connection.indexes and connection.colums rather than class
|
129
|
+
# methods of the referencing class because using the class
|
130
|
+
# methods would require getting the class -- which might trigger
|
131
|
+
# an autoload which could start some recursion making things much
|
132
|
+
# harder to debug.
|
133
|
+
if connection.indexes(referencing_table_name).any?{|index| index.unique && index.columns == [column_name]}
|
134
|
+
macro = :has_one
|
135
|
+
name = names[:has_one]
|
136
|
+
else
|
137
|
+
macro = :has_many
|
138
|
+
name = names[:has_many]
|
139
|
+
if connection.columns(referencing_table_name).any?{ |col| col.name == 'position' }
|
140
|
+
scope_block = lambda { order :position }
|
141
|
+
argstr += "-> { order :position }, "
|
142
|
+
end
|
151
143
|
end
|
152
144
|
end
|
145
|
+
argstr += opts.inspect[1...-1]
|
146
|
+
if (_filter_association(macro, name) && !_method_exists?(name))
|
147
|
+
_create_association(macro, name, argstr, scope_block, opts.dup)
|
148
|
+
end
|
153
149
|
end
|
154
|
-
argstr += opts.inspect[1...-1]
|
155
|
-
if (_filter_association(macro, name) && !_method_exists?(name))
|
156
|
-
_create_association(macro, name, argstr, scope_block, opts.dup)
|
157
|
-
end
|
158
|
-
end
|
159
150
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
151
|
+
def _create_association(macro, name, argstr, *args)
|
152
|
+
logger.debug "[schema_associations] #{self.name || self.from_table.classify}.#{macro} #{name.inspect}, #{argstr}"
|
153
|
+
send macro, name, *args
|
154
|
+
case
|
155
|
+
when respond_to?(:subclasses) then subclasses
|
156
|
+
end.each do |subclass|
|
157
|
+
subclass.send :_create_association, macro, name, argstr, *args
|
158
|
+
end
|
167
159
|
end
|
168
|
-
end
|
169
160
|
|
170
161
|
|
171
|
-
|
162
|
+
def _determine_association_names(reference_name, referencing_name, references_name)
|
172
163
|
|
173
|
-
|
174
|
-
|
164
|
+
references_concise = _concise_name(references_name, referencing_name)
|
165
|
+
referencing_concise = _concise_name(referencing_name, references_name)
|
175
166
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
167
|
+
if _use_concise_name?
|
168
|
+
references = references_concise
|
169
|
+
referencing = referencing_concise
|
170
|
+
else
|
171
|
+
references = references_name
|
172
|
+
referencing = referencing_name
|
173
|
+
end
|
174
|
+
|
175
|
+
case reference_name
|
176
|
+
when 'parent'
|
177
|
+
belongs_to = 'parent'
|
178
|
+
has_one = 'child'
|
179
|
+
has_many = 'children'
|
180
|
+
|
181
|
+
when references_name
|
182
|
+
belongs_to = references
|
183
|
+
has_one = referencing
|
184
|
+
has_many = referencing.pluralize
|
185
|
+
|
186
|
+
when /(.*)_#{references_name}$/, /(.*)_#{references_concise}$/
|
187
|
+
label = $1
|
188
|
+
belongs_to = "#{label}_#{references}"
|
189
|
+
has_one = "#{referencing}_as_#{label}"
|
190
|
+
has_many = "#{referencing.pluralize}_as_#{label}"
|
191
|
+
|
192
|
+
when /^#{references_name}_(.*)$/, /^#{references_concise}_(.*)$/
|
193
|
+
label = $1
|
194
|
+
belongs_to = "#{references}_#{label}"
|
195
|
+
has_one = "#{referencing}_as_#{label}"
|
196
|
+
has_many = "#{referencing.pluralize}_as_#{label}"
|
197
|
+
|
198
|
+
else
|
199
|
+
belongs_to = reference_name
|
200
|
+
has_one = "#{referencing}_as_#{reference_name}"
|
201
|
+
has_many = "#{referencing.pluralize}_as_#{reference_name}"
|
202
|
+
end
|
183
203
|
|
184
|
-
|
185
|
-
when 'parent'
|
186
|
-
belongs_to = 'parent'
|
187
|
-
has_one = 'child'
|
188
|
-
has_many = 'children'
|
189
|
-
|
190
|
-
when references_name
|
191
|
-
belongs_to = references
|
192
|
-
has_one = referencing
|
193
|
-
has_many = referencing.pluralize
|
194
|
-
|
195
|
-
when /(.*)_#{references_name}$/, /(.*)_#{references_concise}$/
|
196
|
-
label = $1
|
197
|
-
belongs_to = "#{label}_#{references}"
|
198
|
-
has_one = "#{referencing}_as_#{label}"
|
199
|
-
has_many = "#{referencing.pluralize}_as_#{label}"
|
200
|
-
|
201
|
-
when /^#{references_name}_(.*)$/, /^#{references_concise}_(.*)$/
|
202
|
-
label = $1
|
203
|
-
belongs_to = "#{references}_#{label}"
|
204
|
-
has_one = "#{referencing}_as_#{label}"
|
205
|
-
has_many = "#{referencing.pluralize}_as_#{label}"
|
206
|
-
|
207
|
-
else
|
208
|
-
belongs_to = reference_name
|
209
|
-
has_one = "#{referencing}_as_#{reference_name}"
|
210
|
-
has_many = "#{referencing.pluralize}_as_#{reference_name}"
|
204
|
+
{ :belongs_to => belongs_to.to_sym, :has_one => has_one.to_sym, :has_many => has_many.to_sym }
|
211
205
|
end
|
212
206
|
|
213
|
-
|
214
|
-
|
207
|
+
def _concise_name(string, other)
|
208
|
+
case
|
209
|
+
when string =~ /^#{other}_(.*)$/ then $1
|
210
|
+
when string =~ /(.*)_#{other}$/ then $1
|
211
|
+
when leader = _common_leader(string,other) then string[leader.length, string.length-leader.length]
|
212
|
+
else string
|
213
|
+
end
|
214
|
+
end
|
215
215
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
216
|
+
def _common_leader(string, other)
|
217
|
+
leader = nil
|
218
|
+
other.split('_').each do |part|
|
219
|
+
test = "#{leader}#{part}_"
|
220
|
+
break unless string.start_with? test
|
221
|
+
leader = test
|
222
|
+
end
|
223
|
+
return leader
|
222
224
|
end
|
223
|
-
end
|
224
225
|
|
225
|
-
|
226
|
-
|
227
|
-
other.split('_').each do |part|
|
228
|
-
test = "#{leader}#{part}_"
|
229
|
-
break unless string.start_with? test
|
230
|
-
leader = test
|
226
|
+
def _use_concise_name?
|
227
|
+
schema_associations_config.concise_names?
|
231
228
|
end
|
232
|
-
return leader
|
233
|
-
end
|
234
229
|
|
235
|
-
|
236
|
-
|
237
|
-
|
230
|
+
def _filter_association(macro, name)
|
231
|
+
config = schema_associations_config
|
232
|
+
return false if config.only and not Array.wrap(config.only).include?(name)
|
233
|
+
return false if config.except and Array.wrap(config.except).include?(name)
|
234
|
+
return false if config.only_type and not Array.wrap(config.only_type).include?(macro)
|
235
|
+
return false if config.except_type and Array.wrap(config.except_type).include?(macro)
|
236
|
+
return true
|
237
|
+
end
|
238
238
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
239
|
+
def _get_class_name(name)
|
240
|
+
name = name.dup
|
241
|
+
found = schema_associations_config.table_prefix_map.find { |table_prefix, class_prefix|
|
242
|
+
name.sub! %r[\A#{table_prefix}], ''
|
243
|
+
}
|
244
|
+
name = name.classify
|
245
|
+
name = found.last + name if found
|
246
|
+
name
|
247
|
+
end
|
247
248
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
name.sub! %r[\A#{table_prefix}], ''
|
252
|
-
}
|
253
|
-
name = name.classify
|
254
|
-
name = found.last + name if found
|
255
|
-
name
|
256
|
-
end
|
249
|
+
def _method_exists?(name)
|
250
|
+
method_defined?(name) || private_method_defined?(name) and not (name == :type && [Object, Kernel].include?(instance_method(:type).owner))
|
251
|
+
end
|
257
252
|
|
258
|
-
def _method_exists?(name) #:nodoc:
|
259
|
-
method_defined?(name) || private_method_defined?(name) and not (name == :type && [Object, Kernel].include?(instance_method(:type).owner))
|
260
253
|
end
|
261
254
|
|
262
255
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schema_associations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ronen Barzel
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-03-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: schema_plus_foreign_keys
|
@@ -135,7 +135,6 @@ files:
|
|
135
135
|
- init.rb
|
136
136
|
- lib/schema_associations.rb
|
137
137
|
- lib/schema_associations/active_record/associations.rb
|
138
|
-
- lib/schema_associations/railtie.rb
|
139
138
|
- lib/schema_associations/version.rb
|
140
139
|
- schema_associations.gemspec
|
141
140
|
- schema_dev.yml
|