active_type 0.1.3 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/README.md +175 -3
- data/Rakefile +1 -2
- data/gemfiles/Gemfile.3.2 +1 -1
- data/gemfiles/Gemfile.3.2.lock +11 -11
- data/gemfiles/Gemfile.4.0 +1 -1
- data/gemfiles/Gemfile.4.0.lock +12 -14
- data/gemfiles/Gemfile.4.1 +2 -2
- data/gemfiles/Gemfile.4.1.lock +14 -16
- data/lib/active_type/nested_attributes/association.rb +128 -0
- data/lib/active_type/nested_attributes/builder.rb +67 -0
- data/lib/active_type/nested_attributes/nests_many_association.rb +71 -0
- data/lib/active_type/nested_attributes/nests_one_association.rb +56 -0
- data/lib/active_type/nested_attributes.rb +39 -0
- data/lib/active_type/no_table.rb +25 -12
- data/lib/active_type/object.rb +2 -0
- data/lib/active_type/record.rb +2 -0
- data/lib/active_type/version.rb +1 -1
- data/lib/active_type/virtual_attributes.rb +37 -15
- data/lib/active_type.rb +2 -0
- data/spec/active_type/extended_record_spec.rb +58 -0
- data/spec/active_type/nested_attributes_spec.rb +682 -0
- data/spec/active_type/object_spec.rb +4 -0
- data/spec/active_type/record_spec.rb +21 -0
- data/spec/integration/holidays_spec.rb +102 -0
- data/spec/integration/shape_spec.rb +112 -0
- data/spec/integration/sign_in_spec.rb +3 -0
- data/spec/integration/sign_up_spec.rb +12 -0
- data/spec/shared_examples/defaults.rb +60 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/support/database.rb +0 -5
- metadata +16 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf3e434ef181d6b90bc9254d3d00824e02abc575
|
4
|
+
data.tar.gz: 83883dad267701774625763bde13c919fb89f4f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 749cf455d62f3463b339b3964fb2acad5375cc9357939f2c93fa780045bc233c8e6db7457fb83ba5061f0f9ea78d975ae49b5eeef99135a2571ffa293ebfa9d8
|
7
|
+
data.tar.gz: f4f5718c15b260464619ed1e0cb9c33b6fed16aed6f1503bc7ef8282219cafb852f8d5d6d5d08b17f4c8ae67f057963b482ef0c9409c12600408411fcdd4e608
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -63,6 +63,7 @@ class SignIn < ActiveType::Object
|
|
63
63
|
attribute :email, :string
|
64
64
|
attribute :date_of_birth, :date
|
65
65
|
attribute :accepted_terms, :boolean
|
66
|
+
attribute :account_type
|
66
67
|
|
67
68
|
end
|
68
69
|
```
|
@@ -70,11 +71,13 @@ end
|
|
70
71
|
These attributes can be assigned via constructor, mass-assignment, and are automatically typecast:
|
71
72
|
|
72
73
|
```ruby
|
73
|
-
sign_in = SignIn.new(date_of_birth: "1980-01-01", accepted_terms: "1")
|
74
|
+
sign_in = SignIn.new(date_of_birth: "1980-01-01", accepted_terms: "1", account_type: AccountType::Trial.new)
|
74
75
|
sign_in.date_of_birth.class # Date
|
75
76
|
sign_in.accepted_terms? # true
|
76
77
|
```
|
77
78
|
|
79
|
+
ActiveType knows all the types that are allowed in migrations (i.e. `:string`, `:integer`, `:float`, `:decimal`, `:datetime`, `:time`, `:date`, `:boolean`). You can also skip the type to have a virtual attribute without typecasting.
|
80
|
+
|
78
81
|
**`ActiveType::Object` actually inherits from `ActiveRecord::Base`, but simply skips all database access, inspired by [ActiveRecord Tableless](https://github.com/softace/activerecord-tableless).**
|
79
82
|
|
80
83
|
This means your object has all usual `ActiveRecord::Base` methods. Some of those might not work properly, however. What does work:
|
@@ -87,7 +90,9 @@ This means your object has all usual `ActiveRecord::Base` methods. Some of those
|
|
87
90
|
|
88
91
|
### ActiveType::Record
|
89
92
|
|
90
|
-
|
93
|
+
If you have a database backed record (that inherits from `ActiveRecord::Base`), but also want to declare virtual attributes, simply inherit from `ActiveType::Record`.
|
94
|
+
|
95
|
+
Virtual attributes will not be persisted.
|
91
96
|
|
92
97
|
|
93
98
|
### ActiveType::Record[BaseClass]
|
@@ -102,6 +107,168 @@ class SignUp < ActiveType::Record[User]
|
|
102
107
|
end
|
103
108
|
```
|
104
109
|
|
110
|
+
### Inheriting from ActiveType:: objects
|
111
|
+
|
112
|
+
If you want to inherit from an ActiveType class, simply do
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
class SignUp < ActiveType::Record[User]
|
116
|
+
# ...
|
117
|
+
end
|
118
|
+
|
119
|
+
class SpecialSignUp < SignUp
|
120
|
+
# ...
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
### Defaults ####
|
125
|
+
|
126
|
+
Attributes can have defaults. Those are lazily evaluated on the first read, if no value has been set.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
class SignIn < ActiveType::Object
|
130
|
+
|
131
|
+
attribute :created_at, :datetime, default: proc { Time.now }
|
132
|
+
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
The proc is evaluated in the context of the object, so you can do
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
class SignIn < ActiveType::Object
|
140
|
+
|
141
|
+
attribute :email, :string
|
142
|
+
attribute :nickname, :string, default: proc { email.split('@').first }
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
SignIn.new(email: "tobias@example.org").nickname # "tobias"
|
147
|
+
SignIn.new(email: "tobias@example.org", :nickname => "kratob").nickname # "kratob"
|
148
|
+
```
|
149
|
+
|
150
|
+
### Nested attributes
|
151
|
+
|
152
|
+
ActiveType supports its own variant of nested attributes via the `nests_one` /
|
153
|
+
`nests_many` macros. The intention is to be mostly compatible with
|
154
|
+
`ActiveRecord`'s `accepts_nested_attributes` functionality.
|
155
|
+
|
156
|
+
Assume you have a list of records, say representing holidays, and you want to support bulk
|
157
|
+
editing. Then you could do something like:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class Holiday < ActiveRecord::Base
|
161
|
+
validates :date, presence: true
|
162
|
+
end
|
163
|
+
|
164
|
+
class HolidaysForm < ActiveType::Object
|
165
|
+
nests_many :holidays, reject_if: :all_blank, default: proc { Holiday.all }
|
166
|
+
end
|
167
|
+
|
168
|
+
class HolidaysController < ApplicationController
|
169
|
+
def edit
|
170
|
+
@holidays_form = HolidaysForm.new
|
171
|
+
end
|
172
|
+
|
173
|
+
def update
|
174
|
+
@holidays_form = HolidaysForm.new(params[:holidays_form])
|
175
|
+
if @holidays_form.save
|
176
|
+
redirect_to root_url, notice: "Success!"
|
177
|
+
else
|
178
|
+
render :edit
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
# and in the view
|
185
|
+
<%= form_for @holidays_form, url: '/holidays', method: :put do |form| %>
|
186
|
+
<ul>
|
187
|
+
<%= form.fields_for :holidays do |holiday_form| %>
|
188
|
+
<li><%= holiday_form.text_field :date %></li>
|
189
|
+
<% end %>
|
190
|
+
</ul>
|
191
|
+
<% end %>
|
192
|
+
```
|
193
|
+
|
194
|
+
- You have to say `nests_many :records`
|
195
|
+
- `records` will be validated and saved automatically
|
196
|
+
- The generated `.records_attributes =` expects parameters like `ActiveRecord`'s nested attributes, and words together with the `fields_for` helper:
|
197
|
+
|
198
|
+
- either as a hash (where the keys are meaningless)
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
{
|
202
|
+
'1' => { date: "new record's date" },
|
203
|
+
'2' => { id: '3', date: "existing record's date" }
|
204
|
+
}
|
205
|
+
```
|
206
|
+
|
207
|
+
- or as an array
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
{
|
211
|
+
[ date: "new record's date" ],
|
212
|
+
[ id: '3', date: "existing record's date" ]
|
213
|
+
}
|
214
|
+
```
|
215
|
+
|
216
|
+
To use it with single records, use `nests_one`. It works like `accept_nested_attributes` does for `has_one`.
|
217
|
+
|
218
|
+
Supported options for `nests_many` / `nests_one` are:
|
219
|
+
- `build_scope`
|
220
|
+
|
221
|
+
Used to build new records, for example:
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
nests_many :documents, build_scope: proc { Document.where(:state => "fresh") }
|
225
|
+
```
|
226
|
+
|
227
|
+
- `find_scope`
|
228
|
+
|
229
|
+
Used to find existing records (in order to update them).
|
230
|
+
|
231
|
+
- `scope`
|
232
|
+
|
233
|
+
Sets `find_scope` and `build_scope` together.
|
234
|
+
|
235
|
+
If you don't supply a scope, `ActiveType` will guess from the association name, i.e. saying
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
nests_many :documents
|
239
|
+
```
|
240
|
+
|
241
|
+
is the same as saying
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
nests_many :documents, scope: proc { Document }
|
245
|
+
```
|
246
|
+
|
247
|
+
which is identical to
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
nests_many :documents, build_scope: proc { Document }, find_scope: proc { Document }
|
251
|
+
```
|
252
|
+
|
253
|
+
All `...scope` options are evaled in the context of the record on first use, and cached.
|
254
|
+
|
255
|
+
- `allow_destroy`
|
256
|
+
|
257
|
+
Allow to destroy records if the attributes contain `_destroy => '1'`
|
258
|
+
|
259
|
+
- `reject_if`
|
260
|
+
|
261
|
+
Pass either a proc of the form `proc { |attributes| ... }`, or a symbol indicating a method, or `:all_blank`.
|
262
|
+
|
263
|
+
Will reject attributes for which the proc or the method returns true, or with only blank values (for `:all_blank`).
|
264
|
+
|
265
|
+
- `default`
|
266
|
+
|
267
|
+
Initializes the association on first access with the given proc:
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
nests_many :documents, default: proc { Documents.all }
|
271
|
+
```
|
105
272
|
|
106
273
|
|
107
274
|
Supported Rails versions
|
@@ -109,7 +276,12 @@ Supported Rails versions
|
|
109
276
|
|
110
277
|
ActiveType is tested against ActiveRecord 3.2, 4.0 and 4.1.
|
111
278
|
|
112
|
-
Later versions might work, earlier
|
279
|
+
Later versions might work, earlier will not.
|
280
|
+
|
281
|
+
Supported Ruby versions
|
282
|
+
------------------------
|
283
|
+
|
284
|
+
ActiveType is tested against MRI 1.8.7 (for 3.2 only), 1.9.3, 2.0.0, 2.1.2.
|
113
285
|
|
114
286
|
|
115
287
|
Installation
|
data/Rakefile
CHANGED
@@ -11,8 +11,7 @@ namespace :all do
|
|
11
11
|
task :spec do
|
12
12
|
success = true
|
13
13
|
for_each_gemfile do
|
14
|
-
|
15
|
-
success &= system("#{env} bundle exec rspec spec")
|
14
|
+
success &= system("bundle exec rspec spec")
|
16
15
|
end
|
17
16
|
fail "Tests failed" unless success
|
18
17
|
end
|
data/gemfiles/Gemfile.3.2
CHANGED
data/gemfiles/Gemfile.3.2.lock
CHANGED
@@ -1,28 +1,28 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
active_type (0.
|
4
|
+
active_type (0.2.0)
|
5
5
|
activerecord (>= 3.2)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (3.2.
|
11
|
-
activesupport (= 3.2.
|
10
|
+
activemodel (3.2.19)
|
11
|
+
activesupport (= 3.2.19)
|
12
12
|
builder (~> 3.0.0)
|
13
|
-
activerecord (3.2.
|
14
|
-
activemodel (= 3.2.
|
15
|
-
activesupport (= 3.2.
|
13
|
+
activerecord (3.2.19)
|
14
|
+
activemodel (= 3.2.19)
|
15
|
+
activesupport (= 3.2.19)
|
16
16
|
arel (~> 3.0.2)
|
17
17
|
tzinfo (~> 0.3.29)
|
18
|
-
activesupport (3.2.
|
18
|
+
activesupport (3.2.19)
|
19
19
|
i18n (~> 0.6, >= 0.6.4)
|
20
20
|
multi_json (~> 1.0)
|
21
21
|
arel (3.0.3)
|
22
22
|
builder (3.0.4)
|
23
23
|
diff-lcs (1.2.5)
|
24
|
-
i18n (0.6.
|
25
|
-
multi_json (1.
|
24
|
+
i18n (0.6.11)
|
25
|
+
multi_json (1.10.1)
|
26
26
|
rspec (2.14.1)
|
27
27
|
rspec-core (~> 2.14.0)
|
28
28
|
rspec-expectations (~> 2.14.0)
|
@@ -32,7 +32,7 @@ GEM
|
|
32
32
|
diff-lcs (>= 1.1.3, < 2.0)
|
33
33
|
rspec-mocks (2.14.6)
|
34
34
|
sqlite3 (1.3.9)
|
35
|
-
tzinfo (0.3.
|
35
|
+
tzinfo (0.3.40)
|
36
36
|
|
37
37
|
PLATFORMS
|
38
38
|
ruby
|
@@ -40,5 +40,5 @@ PLATFORMS
|
|
40
40
|
DEPENDENCIES
|
41
41
|
active_type!
|
42
42
|
activerecord (~> 3.2.0)
|
43
|
-
rspec
|
43
|
+
rspec (< 2.99)
|
44
44
|
sqlite3
|
data/gemfiles/Gemfile.4.0
CHANGED
data/gemfiles/Gemfile.4.0.lock
CHANGED
@@ -1,34 +1,33 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
active_type (0.
|
4
|
+
active_type (0.2.0)
|
5
5
|
activerecord (>= 3.2)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (4.0.
|
11
|
-
activesupport (= 4.0.
|
10
|
+
activemodel (4.0.8)
|
11
|
+
activesupport (= 4.0.8)
|
12
12
|
builder (~> 3.1.0)
|
13
|
-
activerecord (4.0.
|
14
|
-
activemodel (= 4.0.
|
13
|
+
activerecord (4.0.8)
|
14
|
+
activemodel (= 4.0.8)
|
15
15
|
activerecord-deprecated_finders (~> 1.0.2)
|
16
|
-
activesupport (= 4.0.
|
16
|
+
activesupport (= 4.0.8)
|
17
17
|
arel (~> 4.0.0)
|
18
18
|
activerecord-deprecated_finders (1.0.3)
|
19
|
-
activesupport (4.0.
|
19
|
+
activesupport (4.0.8)
|
20
20
|
i18n (~> 0.6, >= 0.6.9)
|
21
21
|
minitest (~> 4.2)
|
22
22
|
multi_json (~> 1.3)
|
23
23
|
thread_safe (~> 0.1)
|
24
24
|
tzinfo (~> 0.3.37)
|
25
25
|
arel (4.0.2)
|
26
|
-
atomic (1.1.16)
|
27
26
|
builder (3.1.4)
|
28
27
|
diff-lcs (1.2.5)
|
29
|
-
i18n (0.6.
|
28
|
+
i18n (0.6.11)
|
30
29
|
minitest (4.7.5)
|
31
|
-
multi_json (1.
|
30
|
+
multi_json (1.10.1)
|
32
31
|
rspec (2.14.1)
|
33
32
|
rspec-core (~> 2.14.0)
|
34
33
|
rspec-expectations (~> 2.14.0)
|
@@ -38,9 +37,8 @@ GEM
|
|
38
37
|
diff-lcs (>= 1.1.3, < 2.0)
|
39
38
|
rspec-mocks (2.14.6)
|
40
39
|
sqlite3 (1.3.9)
|
41
|
-
thread_safe (0.3.
|
42
|
-
|
43
|
-
tzinfo (0.3.39)
|
40
|
+
thread_safe (0.3.4)
|
41
|
+
tzinfo (0.3.40)
|
44
42
|
|
45
43
|
PLATFORMS
|
46
44
|
ruby
|
@@ -48,5 +46,5 @@ PLATFORMS
|
|
48
46
|
DEPENDENCIES
|
49
47
|
active_type!
|
50
48
|
activerecord (~> 4.0.0)
|
51
|
-
rspec
|
49
|
+
rspec (< 2.99)
|
52
50
|
sqlite3
|
data/gemfiles/Gemfile.4.1
CHANGED
data/gemfiles/Gemfile.4.1.lock
CHANGED
@@ -1,32 +1,31 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
active_type (0.
|
4
|
+
active_type (0.2.0)
|
5
5
|
activerecord (>= 3.2)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (4.1.
|
11
|
-
activesupport (= 4.1.
|
10
|
+
activemodel (4.1.4)
|
11
|
+
activesupport (= 4.1.4)
|
12
12
|
builder (~> 3.1)
|
13
|
-
activerecord (4.1.
|
14
|
-
activemodel (= 4.1.
|
15
|
-
activesupport (= 4.1.
|
13
|
+
activerecord (4.1.4)
|
14
|
+
activemodel (= 4.1.4)
|
15
|
+
activesupport (= 4.1.4)
|
16
16
|
arel (~> 5.0.0)
|
17
|
-
activesupport (4.1.
|
17
|
+
activesupport (4.1.4)
|
18
18
|
i18n (~> 0.6, >= 0.6.9)
|
19
19
|
json (~> 1.7, >= 1.7.7)
|
20
20
|
minitest (~> 5.1)
|
21
21
|
thread_safe (~> 0.1)
|
22
22
|
tzinfo (~> 1.1)
|
23
|
-
arel (5.0.
|
24
|
-
atomic (1.1.16)
|
23
|
+
arel (5.0.1.20140414130214)
|
25
24
|
builder (3.2.2)
|
26
25
|
diff-lcs (1.2.5)
|
27
|
-
i18n (0.6.
|
26
|
+
i18n (0.6.11)
|
28
27
|
json (1.8.1)
|
29
|
-
minitest (5.
|
28
|
+
minitest (5.4.0)
|
30
29
|
rspec (2.14.1)
|
31
30
|
rspec-core (~> 2.14.0)
|
32
31
|
rspec-expectations (~> 2.14.0)
|
@@ -36,9 +35,8 @@ GEM
|
|
36
35
|
diff-lcs (>= 1.1.3, < 2.0)
|
37
36
|
rspec-mocks (2.14.6)
|
38
37
|
sqlite3 (1.3.9)
|
39
|
-
thread_safe (0.3.
|
40
|
-
|
41
|
-
tzinfo (1.1.0)
|
38
|
+
thread_safe (0.3.4)
|
39
|
+
tzinfo (1.2.1)
|
42
40
|
thread_safe (~> 0.1)
|
43
41
|
|
44
42
|
PLATFORMS
|
@@ -46,6 +44,6 @@ PLATFORMS
|
|
46
44
|
|
47
45
|
DEPENDENCIES
|
48
46
|
active_type!
|
49
|
-
activerecord (~> 4.1.0
|
50
|
-
rspec
|
47
|
+
activerecord (~> 4.1.0)
|
48
|
+
rspec (< 2.99)
|
51
49
|
sqlite3
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'active_record/errors'
|
2
|
+
|
3
|
+
module ActiveType
|
4
|
+
|
5
|
+
module NestedAttributes
|
6
|
+
|
7
|
+
class RecordNotFound < ActiveRecord::RecordNotFound; end
|
8
|
+
|
9
|
+
class Association
|
10
|
+
|
11
|
+
def initialize(owner, target_name, options = {})
|
12
|
+
options.assert_valid_keys(:build_scope, :find_scope, :scope, :allow_destroy, :reject_if)
|
13
|
+
|
14
|
+
@owner = owner
|
15
|
+
@target_name = target_name.to_sym
|
16
|
+
@allow_destroy = options.fetch(:allow_destroy, false)
|
17
|
+
@reject_if = options.delete(:reject_if)
|
18
|
+
@options = options.dup
|
19
|
+
end
|
20
|
+
|
21
|
+
def assign_attributes(parent, attributes)
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
def save(parent)
|
26
|
+
keep = assigned_children(parent)
|
27
|
+
changed_children(parent).each do |child|
|
28
|
+
if child.marked_for_destruction?
|
29
|
+
child.destroy if child.persisted?
|
30
|
+
keep.delete(child)
|
31
|
+
else
|
32
|
+
child.save(:validate => false) or raise ActiveRecord::Rollback
|
33
|
+
end
|
34
|
+
end
|
35
|
+
assign_children(parent, keep)
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate(parent)
|
39
|
+
changed_children(parent).each do |child|
|
40
|
+
unless child.valid?
|
41
|
+
child.errors.each do |attribute, message|
|
42
|
+
attribute = "#{@target_name}.#{attribute}"
|
43
|
+
parent.errors[attribute] << message
|
44
|
+
parent.errors[attribute].uniq!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def add_child(parent, child_or_children)
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
|
56
|
+
def assigned_children(parent)
|
57
|
+
Array.wrap(parent[@target_name])
|
58
|
+
end
|
59
|
+
|
60
|
+
def assign_children(parent, children)
|
61
|
+
raise NotImplementedError
|
62
|
+
end
|
63
|
+
|
64
|
+
def changed_children(parent)
|
65
|
+
assigned_children(parent).select(&:changed_for_autosave?)
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_child(parent, attributes)
|
69
|
+
build_scope(parent).new(attributes)
|
70
|
+
end
|
71
|
+
|
72
|
+
def scope(parent)
|
73
|
+
scope_for(parent, :scope) || derive_class_name.constantize
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_scope(parent)
|
77
|
+
scope_for(parent, :build_scope) || scope(parent)
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_scope(parent)
|
81
|
+
scope_for(parent, :find_scope) || scope(parent)
|
82
|
+
end
|
83
|
+
|
84
|
+
def scope_for(parent, key)
|
85
|
+
parent._nested_attribute_scopes ||= {}
|
86
|
+
parent._nested_attribute_scopes[[self, key]] ||= begin
|
87
|
+
scope = @options[key]
|
88
|
+
scope.respond_to?(:call) ? parent.instance_eval(&scope) : scope
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def derive_class_name
|
93
|
+
raise NotImplementedError
|
94
|
+
end
|
95
|
+
|
96
|
+
def fetch_child(parent, id)
|
97
|
+
assigned = assigned_children(parent).detect { |r| r.id == id }
|
98
|
+
return assigned if assigned
|
99
|
+
|
100
|
+
if child = find_scope(parent).find_by_id(id)
|
101
|
+
add_child(parent, child)
|
102
|
+
child
|
103
|
+
else
|
104
|
+
raise RecordNotFound, "could not find a child record with id '#{id}' for '#{@target_name}'"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def truthy?(value)
|
109
|
+
ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
|
110
|
+
end
|
111
|
+
|
112
|
+
def reject?(parent, attributes)
|
113
|
+
result = case @reject_if
|
114
|
+
when :all_blank
|
115
|
+
attributes.all? { |key, value| key == '_destroy' || value.blank? }
|
116
|
+
when Proc
|
117
|
+
@reject_if.call(attributes)
|
118
|
+
when Symbol
|
119
|
+
parent.method(@reject_if).arity == 0 ? parent.send(@reject_if) : parent.send(@reject_if, attributes)
|
120
|
+
end
|
121
|
+
result
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'active_type/nested_attributes/nests_one_association'
|
2
|
+
require 'active_type/nested_attributes/nests_many_association'
|
3
|
+
|
4
|
+
module ActiveType
|
5
|
+
|
6
|
+
module NestedAttributes
|
7
|
+
|
8
|
+
class Builder
|
9
|
+
|
10
|
+
def initialize(owner, mod)
|
11
|
+
@owner = owner
|
12
|
+
@module = mod
|
13
|
+
end
|
14
|
+
|
15
|
+
def build(name, one_or_many, options)
|
16
|
+
add_attribute(name, options.slice(:default))
|
17
|
+
association = build_association(name, one_or_many == :one, options.except(:default))
|
18
|
+
add_writer_method(name, association)
|
19
|
+
add_autosave(name, association)
|
20
|
+
add_validation(name, association)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_association(name, singular, options)
|
27
|
+
(singular ? NestsOneAssociation : NestsManyAssociation).new(@owner, name, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_attribute(name, options)
|
31
|
+
@owner.attribute(name, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_writer_method(name, association)
|
35
|
+
write_method = "#{name}_attributes="
|
36
|
+
@module.module_eval do
|
37
|
+
define_method write_method do |attributes|
|
38
|
+
association.assign_attributes(self, attributes)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_autosave(name, association)
|
44
|
+
save_method = "save_associated_records_for_#{name}"
|
45
|
+
@module.module_eval do
|
46
|
+
define_method save_method do
|
47
|
+
association.save(self)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
@owner.after_save save_method
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_validation(name, association)
|
54
|
+
validate_method = "validate_associated_records_for_#{name}"
|
55
|
+
@module.module_eval do
|
56
|
+
define_method validate_method do
|
57
|
+
association.validate(self)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
@owner.validate validate_method
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|