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 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