flatter-extensions 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +225 -2
- data/lib/flatter/extensions/active_record.rb +15 -3
- data/lib/flatter/extensions/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3ccff2224155cdb168ccd7de33cadeacb42d34b
|
4
|
+
data.tar.gz: 16f61b126dc24a8b68dc04fa8dff925bc2fd6a7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0405d1ce282f152a014365af8c51d26d9f7e18c3f2f8ca5ffac5971e55551e289359a74408f78051e6fbc5f0226092558171c4b8060ad58ca82c95ac11647b7
|
7
|
+
data.tar.gz: a72f133129b400d4c281a0694278d73c5dabd3c7fbe0bdd924787158c717bf68917131457280de05a880aab24f57e62d1068be6b3d3a0246216c70cd00b6832f
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Flatter::Extensions
|
2
2
|
|
3
|
+
[](http://travis-ci.org/akuzko/flatter-extensions)
|
4
|
+
|
3
5
|
A set of extensions to be used with [Flatter](https://github.com/akuzko/flatter) gem.
|
4
6
|
|
5
7
|
## Installation
|
@@ -20,11 +22,232 @@ Or install it yourself as:
|
|
20
22
|
|
21
23
|
## Usage
|
22
24
|
|
23
|
-
|
25
|
+
All extensions can be included at a runtime using `Flatter.use` method. Usually,
|
26
|
+
this is done in your app initializer with a `Flatter.configure`, like so:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
Flatter.configure do |f|
|
30
|
+
f.use :order
|
31
|
+
f.use :skipping
|
32
|
+
f.use :active_record
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
Bellow is a list of available extensions with description.
|
37
|
+
|
38
|
+
### Multiparam
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
Flatter.use :multiparam
|
42
|
+
```
|
43
|
+
|
44
|
+
Allows you to define multiparam mappings by using `:multiparam` option to mapping.
|
45
|
+
Works pretty much like `Rails` multiparam attribute assignment:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class PersonMapper < Flatter::Mapper
|
49
|
+
map :first_name, :last_name
|
50
|
+
map dob: :date_of_birth, multiparam: Date
|
51
|
+
end
|
52
|
+
|
53
|
+
# ...
|
54
|
+
|
55
|
+
mapper = PersonMapper.new(person)
|
56
|
+
mapper.write(first_name: 'John', 'dob(1i)' => '2015', 'dob(2i)' => '01', 'dob(3i)' => '15')
|
57
|
+
person.date_of_birth # => Thu, 15 Jan 2015
|
58
|
+
```
|
59
|
+
|
60
|
+
### Skipping
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
Flatter.use :skipping
|
64
|
+
```
|
65
|
+
|
66
|
+
Allows to skip mappers (mountings) from the processing chain by calling `skip!`
|
67
|
+
method on a particular mapper. This is usually used in before callbacks to
|
68
|
+
avoid processing specific mappers if they fail to match some processing condition.
|
69
|
+
For example:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class Person < ActiveRecord::Base
|
73
|
+
has_many :phones
|
74
|
+
end
|
75
|
+
|
76
|
+
class PhoneMapper < Flatter::Mapper
|
77
|
+
map phone_number: :number
|
78
|
+
|
79
|
+
validates_presence_of :phone_number
|
80
|
+
end
|
81
|
+
|
82
|
+
class PersonMapper < Flatter::Mapper
|
83
|
+
mount :phone, foreign_key: :person_id
|
84
|
+
|
85
|
+
set_callback :validate, :before, :skip_empty_phone
|
86
|
+
|
87
|
+
def skip_empty_phone
|
88
|
+
# avoids validation and creation of new phone number
|
89
|
+
# if provided `phone_number` field was blank.
|
90
|
+
mounting(:phone).skip! if phone_number.blank?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
### Order
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
Flatter.use :order
|
99
|
+
```
|
100
|
+
|
101
|
+
Allows you to manually control processing order of mappers and their mountings.
|
102
|
+
Provides `:index` option for mountings, which can be either a Number, which means
|
103
|
+
order for both validation and saving routines, or a hash like `{validate: -1, save: 2}`.
|
104
|
+
By default all mappers have index of `0` and processed from top to bottom.
|
105
|
+
|
106
|
+
This extension will be very handy when using with `:active_record` extension, since
|
107
|
+
all targets (records) are saved **without** callbacks and validation, which means
|
108
|
+
you won't have such things as associations autosave. That means that to properly
|
109
|
+
save records with foreign key dependencies, you have to do it in proper order.
|
110
|
+
For example, in following scenario we use `PersonMapper` to manage people. If
|
111
|
+
additionally `email` was supplied, `User` record will be created, and `Person`
|
112
|
+
record will be associated with it. This means that we need to skip User *before*
|
113
|
+
validation if there was no email provided, but save it *before* person mounter.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
class Person < ActiveRecord::Base
|
117
|
+
belongs_to :user
|
118
|
+
end
|
119
|
+
|
120
|
+
class User < ActiveRecord::Base
|
121
|
+
has_one :person
|
122
|
+
end
|
123
|
+
|
124
|
+
class UserMapper < Flatter::Mapper
|
125
|
+
map :email
|
126
|
+
|
127
|
+
validates_presence_of :email
|
128
|
+
end
|
129
|
+
|
130
|
+
class PersonMapper < Flatter::Mapper
|
131
|
+
trait :management do
|
132
|
+
mount :user, index: {save: -1}, mounter_foreign_key: :user_id
|
133
|
+
|
134
|
+
set_callback :validate, :before, :skip_user
|
135
|
+
|
136
|
+
def skip_user
|
137
|
+
mounting(:user).skip! if email.blank?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
### ActiveRecord
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
Flatter.use :active_record
|
147
|
+
```
|
148
|
+
|
149
|
+
Probably, the most important extension, the reason why Flatter (former FlatMap)
|
150
|
+
was initially built. This extension allows you to build mappers that will handle
|
151
|
+
complexity of ActiveRecord associations in a graph of related records to provide a
|
152
|
+
single mapper object with plain hash of attributes that can be used to render a form,
|
153
|
+
used as a form object itself to distribute form params among records, or used in
|
154
|
+
your API, encapsulating processing logic with reusable traits.
|
155
|
+
|
156
|
+
`:active_record` extension depends on `:skipping` extension to be able to utilize
|
157
|
+
it properly. This means that if you use `:active_record` extension, `:skipping`
|
158
|
+
will be used automatically. Although there will be no harm using it explicitly
|
159
|
+
like:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
Flatten.configure do |f|
|
163
|
+
f.use :order
|
164
|
+
f.use :skipping
|
165
|
+
f.use :active_record
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
When using `:active_record` extension, you should keep in mind following things:
|
170
|
+
|
171
|
+
#### Mounted target from association
|
172
|
+
|
173
|
+
If mapper's target is an `ActiveRecord::Base` object, target for mounted mappers
|
174
|
+
will be tried to be derived from relevant association. For example:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
class Person < ActiveRecord::Base
|
178
|
+
belongs_to :user
|
179
|
+
has_one :location
|
180
|
+
has_many :phones
|
181
|
+
end
|
182
|
+
|
183
|
+
class PersonMapper < Flatter::Mapper
|
184
|
+
mount :user
|
185
|
+
mount :location
|
186
|
+
mount :phone
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
Here we have:
|
191
|
+
- `:user` is a `:belongs_to` association. Target for mounted `UserMapper` will
|
192
|
+
be by default fetched as `person.user || person.build_user`.
|
193
|
+
- `:location` is a `:has_one` association. Just like `:user`, target for mounted
|
194
|
+
`LocationMapper` will be fetched as `person.location || person.build_location`.
|
195
|
+
- `:phones` is a `:has_many` association, and we map **singular** phone. In this
|
196
|
+
case target for `PhoneMapper` is fetched as `person.phones.build`. Thus, you may
|
197
|
+
want to `skip!` this mounting before save or validation to prevent creating
|
198
|
+
freshly-built record, if it was not populated with any values.
|
199
|
+
|
200
|
+
Mounting collection associations and working with them as with collections is
|
201
|
+
not supported for now.
|
202
|
+
|
203
|
+
Keep in mind that you can always pass `:target` option to control targets of
|
204
|
+
mounted mappers.
|
205
|
+
|
206
|
+
#### Saving is performed without callbacks and validation
|
207
|
+
|
208
|
+
That's right. On save your models will not be validated and their `:save` callbacks
|
209
|
+
will not be executed. All such form-related business logic should be handled by mappers,
|
210
|
+
keeping models clean for taking care about inner application business logic only.
|
211
|
+
|
212
|
+
For example, if you have multi-step form and want to put all your validations in
|
213
|
+
model, there will be dozens of boolean checks to use specific validations only
|
214
|
+
on specific steps. With mappers, you can define necessary sets of validations
|
215
|
+
within traits and keep your models clean.
|
216
|
+
|
217
|
+
#### Processing order and foreign keys
|
218
|
+
|
219
|
+
Since there are no callbacks, there will be no association autosaving, which means
|
220
|
+
that your models will be saved exactly once exactly when each mapper starts it's
|
221
|
+
saving routines. That also means that you should manually handle foreign keys
|
222
|
+
assigning when creating new records. This can be done via before- or after-save
|
223
|
+
callbacks, but extension provides a handy mounting options to do it for you:
|
224
|
+
`foreign_key` and `mounter_foreign_key`. First option should be set when mounted
|
225
|
+
mapper depends on current one. And the second one - when current mapper depends on
|
226
|
+
mounted one. In that case `:index` option should be used to force this mounting
|
227
|
+
to be processed first:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
class PersonMapper < Flatter::Mapper
|
231
|
+
mount :user, mounter_foreign_key: :user_id, index: -1
|
232
|
+
mount :phone, foreign_key: :person_id
|
233
|
+
end
|
234
|
+
```
|
235
|
+
|
236
|
+
#### Transactions
|
237
|
+
|
238
|
+
With `:active_record` extension you should mainly use `apply(params)` method for
|
239
|
+
updating or creating your models via mappers. It wraps whole saving process (writing
|
240
|
+
values, validation and saving) in a transaction. The reason for this is that your
|
241
|
+
mappings may have custom db-mutating writers, and if saving fails, such mutations
|
242
|
+
should be rolled back. However, `save` method is also wrapped in transaction and
|
243
|
+
will return `false` if any mapper in processing chain will fail to save it's
|
244
|
+
target. This might happen due to DB constraints, for example.
|
24
245
|
|
25
246
|
## Development
|
26
247
|
|
27
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
248
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
249
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
250
|
+
prompt that will allow you to experiment.
|
28
251
|
|
29
252
|
## Contributing
|
30
253
|
|
@@ -59,8 +59,8 @@ if defined? ActiveRecord
|
|
59
59
|
|
60
60
|
def reflection_from_target(target)
|
61
61
|
target_class = target.class
|
62
|
-
reflection = target_class.reflect_on_association(name)
|
63
|
-
reflection || target_class.reflect_on_association(name.pluralize)
|
62
|
+
reflection = target_class.reflect_on_association(name.to_sym)
|
63
|
+
reflection || target_class.reflect_on_association(name.pluralize.to_sym)
|
64
64
|
end
|
65
65
|
private :reflection_from_target
|
66
66
|
end
|
@@ -69,7 +69,19 @@ if defined? ActiveRecord
|
|
69
69
|
def apply(*)
|
70
70
|
return super unless ar?
|
71
71
|
|
72
|
-
|
72
|
+
!!::ActiveRecord::Base.transaction do
|
73
|
+
super or raise ::ActiveRecord::Rollback
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def save
|
78
|
+
!!::ActiveRecord::Base.transaction do
|
79
|
+
begin
|
80
|
+
super
|
81
|
+
rescue ::ActiveRecord::StatementInvalid
|
82
|
+
raise ::ActiveRecord::Rollback
|
83
|
+
end
|
84
|
+
end
|
73
85
|
end
|
74
86
|
|
75
87
|
def save_target
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flatter-extensions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Artem Kuzko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: flatter
|