flatter-extensions 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 836dcba21ba1e7372ffb7ba02c0c830caab448da
4
- data.tar.gz: d6a27a477a4de896ab938cc9098b5e7e60ebabce
3
+ metadata.gz: a3ccff2224155cdb168ccd7de33cadeacb42d34b
4
+ data.tar.gz: 16f61b126dc24a8b68dc04fa8dff925bc2fd6a7b
5
5
  SHA512:
6
- metadata.gz: 3b456cbd8bc8a4064969f0f3182951c779c12b58836d8821f6a1a6ac5737543349c7d16f58ecbeabad18eaec8d9c2a8e81f472a778c65eaedfd59d7c8019c14e
7
- data.tar.gz: 44cceaca1a35835e7b0d29be5840baf3edd7747c033f2351e60c53d5ad99d47b97696ca161a475dfb05cd792a1fd9ac1d0924bdd763f1ee0e832cc815571ed38
6
+ metadata.gz: a0405d1ce282f152a014365af8c51d26d9f7e18c3f2f8ca5ffac5971e55551e289359a74408f78051e6fbc5f0226092558171c4b8060ad58ca82c95ac11647b7
7
+ data.tar.gz: a72f133129b400d4c281a0694278d73c5dabd3c7fbe0bdd924787158c717bf68917131457280de05a880aab24f57e62d1068be6b3d3a0246216c70cd00b6832f
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Flatter::Extensions
2
2
 
3
+ [![Build Status](https://secure.travis-ci.org/akuzko/flatter-extensions.png)](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
- Coming soon.
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 `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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
- ::ActiveRecord::Base.transaction{ super }
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
@@ -1,5 +1,5 @@
1
1
  module Flatter
2
2
  module Extensions
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.1"
4
4
  end
5
5
  end
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.0
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-03 00:00:00.000000000 Z
11
+ date: 2015-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: flatter