flatter 0.1.0 → 0.2.0

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: f1e1af6544620264587af098834b94ddf548bac7
4
- data.tar.gz: c48b5d92d0692cb453c84a84782dcfa77ea4c5f7
3
+ metadata.gz: 543de04a22f3559983f3dd01ee360a7f78117df6
4
+ data.tar.gz: 5e6e173e3c3b60d0109ddb9a898b61fcd756254f
5
5
  SHA512:
6
- metadata.gz: 05a93c13278b98f3eeb51f27095575d39a713c13c7933c6863a4f9b0cd800709b7de781afd6e36dc147021bfbd459a0977e194efc739d1a4fbec62e4c92cbce6
7
- data.tar.gz: f9b66855aab233cd339eb94315abb527a81bd3cf188c8a05b679866ddf82c076264b2e906cc131cd174ea19c2a3223e2a06fcc1001c4f4c9ead9aa230df67dfd
6
+ metadata.gz: 8d818795d50e0d5a138e430ad5b1e5c3b4983e93bba7f5a34561592d92a35ca17ac57e3d340f7674f3662602e6a290fcadf7a78482f6b9fb66bdcc288d02655a
7
+ data.tar.gz: 052590d63bd471a285e3268497189bf14185d9bd24b1a69058a363b550f4370c590239abe7277d29f91247a20b180146ab34bf3264be689897ad1b7bfca71687
data/README.md CHANGED
@@ -1,7 +1,10 @@
1
1
  # Flatter
2
2
 
3
- This gem supersedes [FlatMap](https://github.com/TMXCredit/flat_map) gem. With only it's core concepts in mind
4
- it has been written from complete scratch to provide more pure, clean, extensible code and reliable functionality.
3
+ [![Build Status](https://secure.travis-ci.org/akuzko/flatter.png)](http://travis-ci.org/akuzko/flatter)
4
+
5
+ This gem supersedes [FlatMap](https://github.com/TMXCredit/flat_map) gem. With
6
+ only it's core concepts in mind it has been written from complete scratch to
7
+ provide more pure, clean, extensible code and reliable functionality.
5
8
 
6
9
  ## Installation
7
10
 
@@ -21,11 +24,683 @@ Or install it yourself as:
21
24
 
22
25
  ## Usage
23
26
 
24
- Coming soon.
27
+ If you happen to use `FlatMap` gem , check out
28
+ [Flatter and FlatMap: What's Changed](https://github.com/akuzko/flatter/wiki/Flatter-and-FlatMap:-What's-Changed) wiki page.
29
+
30
+ Flatter's main working units are instances of `Mapper` class. **Mappers** are essentially
31
+ wrappers around your related ActiveModel-like objects, map their attributes to mapper's
32
+ accessors via **mappings**, can be **mounted** by other mappers, and can define flexible
33
+ behavior via **traits**. Let's cover this topics one by one.
34
+
35
+ ### Mappings
36
+
37
+ Mappings represent a mapper's property, which maps it to some attribute of the
38
+ target object. Since eventually mappers are used in combination with each other, it is
39
+ better to map model's attribute with a unique "full name" to avoid collisions, for example:
40
+
41
+ ```ruby
42
+ # models:
43
+ class Person
44
+ include ActiveModel::Model
45
+
46
+ attr_accessor :first_name, :last_name
47
+ end
48
+
49
+ class Group
50
+ include ActiveModel::Model
51
+
52
+ attr_accessor :name
53
+ end
54
+
55
+ class Department
56
+ include ActiveModel::Model
57
+
58
+ attr_accessor :name
59
+ end
60
+
61
+ # mappers:
62
+ class PersonMapper < Flatter::Mapper
63
+ map :first_name, :last_name
64
+ # it's ok, since :first_name and :last_name attributes are
65
+ # not likely to be used somewhere else
66
+ end
67
+
68
+ class GroupMapper < Flatter::Mapper
69
+ map group_name: :name
70
+ # maps mapper's :group_name attribute to target's :name attribute
71
+ end
72
+
73
+ class DepartmentMapper < Flatter::Mapper
74
+ map department_name: :name
75
+ # maps mapper's :department_name attribute to target's :name attribute
76
+ end
77
+ ```
78
+
79
+ #### Mapping Options
80
+
81
+ - `:reader` Allows to add a custom logic for reading target's attribute.
82
+ When value is `String` or `Symbol`, calls a method defined by a **mapper** class.
83
+ If that method accepts an argument, mapping name will be passed to it.
84
+ When value is `Proc`, it is executed in context of mapper object, yielding
85
+ mapping name if block has arity of 1. For other arbitrary objects will simply
86
+ return that object.
87
+
88
+ - `:writer` Allows to control a way how value is assigned (written).
89
+ When value is `String` or `Symbol`, calls a method defined by a **mapper** class,
90
+ passing a value to it. If that method accepts second argument, mapping name will be
91
+ additionally passed to it.
92
+ When value is `Proc`, it is executed in context of mapper object, yielding
93
+ value and optionally mapping name if block has arity of 2. For other values will
94
+ raise error.
95
+
96
+ ### Mountings
97
+
98
+ Stand-alone mappers provide not very much benefit. However, mappers have a powerful
99
+ ability to be mounted on top of each other. When mapper mounts another one, it
100
+ gains access to all of it's mappings, and they become accessible in a plain way.
101
+
102
+ For example, having `Person`, `Department` and `Group` classes defined above with
103
+ additional sample relationship we might have:
104
+
105
+ ```ruby
106
+ # models:
107
+ class Person
108
+ def department
109
+ @department ||= Department.new(name: 'Default')
110
+ end
111
+
112
+ def group
113
+ @group ||= Group.new(name: 'General')
114
+ end
115
+ end
116
+
117
+ # mappers:
118
+ class PersonMapper < Flatter::Mapper
119
+ map :first_name, :last_name
120
+
121
+ mount :department
122
+ mount :group
123
+ end
124
+
125
+ person = Person.new(first_name: 'John', last_name: 'Smith')
126
+ mapper = PersonMapper.new(person)
127
+
128
+ mapper.read # =>
129
+ # { 'first_name' => 'John',
130
+ # 'last_name' => 'Smith',
131
+ # 'department_name' => 'Default',
132
+ # 'group_name' => 'General' }
133
+
134
+ mapper.group_name = 'Managers'
135
+ person.group.name # => "Managers"
136
+ ```
137
+
138
+ #### Mounting Options
139
+
140
+ - `:mapper_class_name` Name of the mapper class (`String`) if it cannot be
141
+ determined from the mounting name itself. By default it is camelized name
142
+ followed by 'Mapper', for example, for `:group` mounting, default mapper
143
+ class name is `'GroupMapper'`.
144
+
145
+ - `:mapper_class` Used mostly internally, but allows to specify mapper class
146
+ itself. Has more priority than `:mapper_class_name` option.
147
+
148
+ - `:target` Allows to manually set mounted mapper's target. By default target is
149
+ obtained from mounting mapper's target by sending it mounting name. In example
150
+ above target for `:group` mapping was obtained by sending `:group` method to
151
+ `person` object, which was the target of root mapper.
152
+ When value is `String` or `Symbol`, it is considered as a method name of the
153
+ **mapper**, which is called with no arguments.
154
+ When value is `Proc`, it is called yielding mapper's target to it.
155
+ For other objects, objects themselves are used as targets.
156
+
157
+ - `:traits` Allows to specify a list of traits to be applied for mounted mappers.
158
+ See **Traits** section bellow.
159
+
160
+ ### Callbacks
161
+
162
+ Mappers include `ActiveModel::Validation` module and thus support `ActiveSupport`'s
163
+ callbacks. Additionally, `:save` callbacks have been defined for `Flatter::Mapper`,
164
+ so you can do something like `set_callback :save, :after, :send_invitation`.
165
+
166
+ ### Traits
167
+
168
+ Traits are another powerful mapper ability. Traits allow to encapsulate named sets
169
+ of additional definitions, and optionally use them on mapper initialization or
170
+ when mounting mapper in other one. Everything that can be defined within the mapper
171
+ can be defined withing the trait. For example (suppose we have some additional
172
+ `:with_counts` trait defined on `DepartmentMapper` alongside with model relationships):
173
+
174
+ ```ruby
175
+ class PersonMapper < Flatter::Mapper
176
+ map :first_name, :last_name
177
+
178
+ trait :full_info do
179
+ map :middle_name, dob: :date_of_birth
180
+
181
+ mount :group
182
+ end
183
+
184
+ trait :with_department do
185
+ mount :department, traits: :with_counts
186
+ end
187
+ end
188
+
189
+ mapper = PersonMapper.new(person)
190
+ full_mapper = PersonMapper.new(person, :full_info, :with_department)
191
+
192
+ mapper.read # =>
193
+ # { 'first_name' => 'John',
194
+ # 'last_name' => 'Smith' }
195
+
196
+ full_mapper.read # =>
197
+ # { 'first_name' => 'John',
198
+ # 'last_name' => 'Smith',
199
+ # 'middle_name' => nil,
200
+ # 'dob' => Wed, 18 Feb 1981,
201
+ # 'group_name' => 'General'
202
+ # 'department_name' => 'Default',
203
+ # 'department_people_count' => 31 }
204
+ ```
205
+
206
+ #### Traits and callbacks
207
+
208
+ Since traits are internally mappers (which allows you to define everything mapper
209
+ can), you can also define callbacks on traits, allowing you to dynamically opt-in,
210
+ opt-out and reuse functionality. Keep in mind that `ActiveModel`'s validation
211
+ routines are also just a set of callbacks, meaning that you can define sets of
212
+ validation in traits, mix them together in any way. For example:
213
+
214
+ ```ruby
215
+ class PersonMapper < Flatter::Mapper
216
+ map :first_name, :last_name
217
+
218
+ trait :registration do
219
+ map personal_email: :email
220
+
221
+ validates_presence_of :first_name, :last_name
222
+ validates :personal_email, :presence: true, email: true
223
+
224
+ set_callback :save, :after, :send_greeting
225
+
226
+ def send_greeting
227
+ PersonMailer.greeting(target).deliver_now
228
+ end
229
+ end
230
+ end
231
+ ```
232
+
233
+ #### Traits and shared methods
234
+
235
+ Despite the fact traits are separate objects, you can call methods defined in
236
+ one trait from another trait, as well as methods defined in root mapper itself
237
+ (such as attribute methods). That allows you to treat traits as parts of the
238
+ root mapper.
239
+
240
+ #### Inline extension traits
241
+
242
+ When initializing a mapper, or defining a mounting, you can pass a block with
243
+ additional definitions. This block will be treated as an anonymous extension trait.
244
+ For example, let's suppose that `email` from example above is actually a part
245
+ of another `User` model that has it's own `UserMapper` with defined `:email` mapping.
246
+ Then we might have something like:
247
+
248
+ ```ruby
249
+ class PersonMapper < Flatter::Mapper
250
+ map :first_name, :last_name
251
+
252
+ trait :registration do
253
+ validates_presence_of :first_name, :last_name
254
+
255
+ mount :user do
256
+ validates :email, :presence: true, email: true
257
+ set_callback :save, :after, :send_greeting
258
+
259
+ def send_greeting
260
+ UserMailer.greeting(target).deliver_now
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ ```
267
+
268
+ ### Processing Order
269
+
270
+ `Flatter` mappers have a well-defined processing order of mountings (including
271
+ traits), best shown by example. Suppose we have something like this:
272
+
273
+ ```ruby
274
+ class AMapper < Flatter::Mapper
275
+ trait :trait_a1 do
276
+ mount :b, traits: :trait_b do
277
+ # extension callbacks definitions
278
+ end
279
+ end
280
+
281
+ trait :trait_a2 do
282
+ mount :c
283
+ end
284
+
285
+ mount :d
286
+ end
287
+ ```
288
+
289
+ Mappers are processed (validated and saved) from top to bottom. Let's have initialized
290
+
291
+ ```ruby
292
+ mapper = AMapper.new(a, :trait_a2, :trait_a1)
293
+ ```
294
+
295
+ Please note traits order, it is very important: `:trait_a2` goes first, so it's
296
+ callbacks and mountings will go first too. So if we call `mapper.save`, we will have
297
+ following execution order (suppose, we have defined callbacks for all traits and mappers):
298
+
299
+ ```
300
+ trait_a2.before_save
301
+ trait_a1.before_save
302
+ A.before_save
303
+ A.save
304
+ A.after_save
305
+ trait_a1.after_save
306
+ trait_a2.after_save
307
+ C.before_save
308
+ C.save
309
+ C.after_save
310
+ trait_b.before_save
311
+ B_extension.before_save
312
+ B.before_save
313
+ B.save
314
+ B.after_save
315
+ B_extension.after_save
316
+ trait_b.after_save
317
+ D.before_save
318
+ D.save
319
+ D.after_save
320
+ ```
321
+
322
+ ### Attribute methods
323
+
324
+ All mappers can access mapped values via attribute methods that match mapping names.
325
+ That allows you to easily use mappers for building forms or developing other
326
+ functionality.
327
+
328
+ You also have reader methods that match mounting names. They will return
329
+ value read for a specific mounting (including it's own nested mountings).
330
+ For example:
331
+
332
+ ```ruby
333
+ class UserMapper < Flatter::Mapper
334
+ map :email
335
+
336
+ mount :person do
337
+ map :first_name, :last_name
338
+
339
+ mount :phone do
340
+ map phone_number: :number
341
+ end
342
+ end
343
+ end
344
+
345
+ mapper = UserMapper.new(User.new)
346
+ mapper.email = "user@email.com"
347
+ mapper.first_name = "John"
348
+ mapper.phone_number = "111-222-3333"
349
+
350
+ mapper.read # =>
351
+ # { "email" => "user@email.com",
352
+ # "first_name" => "John",
353
+ # "last_name" => nil,
354
+ # "phone_number" => "111-222-3333" }
355
+
356
+ mapper.person # =>
357
+ # { "first_name" => "John",
358
+ # "last_name" => nil,
359
+ # "phone_number" => "111-222-3333" }
360
+
361
+ mapper.phone # =>
362
+ # { "phone_number" => "111-222-3333" }
363
+ ```
364
+
365
+ Please also read "Attribute methods" subsection for Collections bellow
366
+ for details on what methods do you get when mapping collections.
367
+
368
+ ### Collections
369
+
370
+ Starting from version `0.2.0`, Flatter mappers also support handling of collections.
371
+
372
+ #### Declaration
373
+
374
+ To declare a mapper that will handle a collection of items, simply mount it
375
+ with a pluralized name:
376
+
377
+ ```ruby
378
+ class PersonMapper < Flatter::Mapper
379
+ mount :phones
380
+ end
381
+ ```
382
+
383
+ If you need to mount a mapper with already pluralized name to handle single
384
+ item in common fashion, mount it with `collection: false` option:
385
+
386
+ ```ruby
387
+ class SeamstressMapper < Flatter::Mapper
388
+ mount :scissors, collection: false
389
+ end
390
+ ```
391
+
392
+ If you need your root mapper to handle a collection of items, initialize it
393
+ with `collection: true` option:
394
+
395
+ ```ruby
396
+ mapper = PhoneMapper.new(user.phones, collection: true)
397
+ ```
398
+
399
+ #### Key
400
+
401
+ Mapper that will be used for mapping collection should define `key` mapping.
402
+ `Flatter` offers `key` class-level method to do it easier. You can call it
403
+ on mapper definition:
404
+
405
+ ```ruby
406
+ class PhoneMapper
407
+ key :id
408
+ end
409
+ ```
410
+
411
+ or when mounting mapper for collection handling:
412
+
413
+ ```ruby
414
+ class PersonMapper
415
+ mount :phones do
416
+ key -> { target.number }
417
+ end
418
+ end
419
+ ```
420
+
421
+ All non-nil `key` mappings have to have unique value (within collection they
422
+ belong to). Otherwise `NonUniqKeysError` will be raised on reading. All items
423
+ that have `nil` as a key value are considered to be "new items". All such
424
+ items are removed from collection on writing.
425
+
426
+ #### Reading
427
+
428
+ As well as can be expected, collection mappers provide an array of hashes
429
+ derived from reading from all items in the collection. Each hash in this array
430
+ will have `"key"` key for item identification. It should be used for writing
431
+ (see bellow). For example:
432
+
433
+ ```ruby
434
+ class CompanyMapper < Flatter::Mapper
435
+ map company_name: :name
436
+
437
+ mount :departments do
438
+ key :id
439
+
440
+ mount :location
441
+ end
442
+ end
443
+
444
+ class DepartmentMapper < Flatter::Mapper
445
+ map department_name: :name
446
+ end
447
+
448
+ class LocationMapper < Flatter::Mapper
449
+ map location_name: :name
450
+ end
451
+
452
+ # ...
453
+
454
+ mapper = CompanyMapper.new(company)
455
+
456
+ mapper.read # =>
457
+ # { "company_name" => "Web Developers, Inc.",
458
+ # "departments" => [{
459
+ # "key" => 1,
460
+ # "department_name" => "R & D",
461
+ # "location_name" => "Good Office"
462
+ # }, {
463
+ # "key" => 2,
464
+ # "department_name" => "QA",
465
+ # "location_name" => "QA Office"
466
+ # }]
467
+ # }
468
+
469
+ ```
470
+
471
+ #### Writing
472
+
473
+ To update collection items, you should pass an array of hashes to it's mapper.
474
+ Value of the `:key` key of each hash is important and defines how each set of
475
+ params will be used.
476
+
477
+ - If `key` is present in the original collection, `params` hash will be used
478
+ to update mapped item via `write` method
479
+
480
+ - If `key` is `nil`, params are treated as attributes for the new record, so
481
+ new instance of mapped target class is created and updated via `write` method.
482
+
483
+ - In original collection, *all* items with keys that are not listed in given
484
+ array of hash params considered to be marked for destruction and corresponding
485
+ items will be removed from mapped collection. The same concerns for *all*
486
+ current items in collection, which have `key` mapped to `nil`.
487
+
488
+ Example:
489
+
490
+ ```ruby
491
+ company.departments.map(&:id) # => [1, 2]
492
+ company.departments.map(&:name) # => ["R & D", "QA"]
493
+
494
+ company_mapper.write(departments: [
495
+ {key: 1, department_name: "D & R"},
496
+ {department_name: "Testers"}
497
+ ])
498
+
499
+ company.departments.map(&:id) # => [1, nil]
500
+ company.departments.map(&:name) # => ["D & R", "Testers"]
501
+ ```
502
+
503
+ #### Attribute Methods
504
+
505
+ When you use mappers to map collection of items, attribute method behavior
506
+ is slightly different. For example, when you have
507
+
508
+ ```ruby
509
+ class PersonMapper < Flatter::Mapper
510
+ map :first_name, :last_name
511
+ mount :phone
512
+ end
513
+
514
+ class DepartmentMapper < Flatter::Mapper
515
+ mount :people do
516
+ key :id
517
+ end
518
+ end
519
+ ```
520
+
521
+ `mapper.first_name` no longer able to return specific value, since it's
522
+ not clear which first name should it be. Thus, when mapper is mounted as
523
+ a collection item, instead of singular value accessors you gain pluralized
524
+ reader methods:
525
+
526
+ ```ruby
527
+ # all first_names of all people of the mapped department:
528
+ mapper.first_names # => ["John", "Derek"]
529
+ ```
530
+
531
+ The same concerns for all nested (singular or collection) mappings and mountings
532
+ under collection mapper:
533
+
534
+ ```ruby
535
+ # all phone number of all people of the mapped department
536
+ mapper.phone_numbers # => ["111-222-3333", "222-111-33333"]
537
+
538
+ # all the people
539
+ mapper.people # =>
540
+ # [{"first_name" => "John", "last_name" => "Smith", "key" => 1, "phone_number" => "111-222-3333"},
541
+ # {"first_name" => "Derek", "last_name" => "Parker", "key" => 2, "phone_number" => "222-111-3333"}]
542
+
543
+ # all phones (note the :phone mapper mounted on :people, opposed to it's :phone_number mapping)
544
+ mapper.phones # =>
545
+ # [{"phone_number" => "111-222-3333"}, {"phone_number" => "222-111-33333"}]
546
+ ```
547
+
548
+ Please note that attempt to use writer method to update collection of mappings,
549
+ such as `first_names=` will raise runtime `"Cannot directly write to a collection"`
550
+ error. To update collection items and their data you have to use `write`/`apply`
551
+ methods to utilize `key`-dependent logic to properly update your collection items
552
+ alongside with all nested mappings/mountings they might have.
553
+
554
+ #### Errors
555
+
556
+ Since all errors after validation process are consolidated into a plain hash
557
+ of errors, there is a need to distinct errors of one collection items from
558
+ another ones. To achieve this, Flatter adds special prefix to error key, which is
559
+ formed from collection name and item **index** (not id or key). For example:
560
+
561
+ ```ruby
562
+ class Person
563
+ include ActiveModel::Model
564
+
565
+ attr_accessor :name, :age
566
+ end
567
+
568
+ class Department
569
+ include ActiveModel::Model
570
+
571
+ attr_accessor :name
572
+
573
+ def people
574
+ @people ||= []
575
+ end
576
+ end
577
+
578
+ class PersonMapper < Flatter::Mapper
579
+ map :age, person_name: :name
580
+
581
+ validates :age, numericality: {only_integer: true, greater_than_or_equal_to: 1}
582
+ end
583
+
584
+ class DepartmentMapper < Flatter::Mapper
585
+ map department_name: :name
586
+
587
+ mount :people
588
+ end
589
+
590
+ department = Department.new
591
+ mapper = DepartmentMapper.new(department)
592
+ mapper.apply(people: [
593
+ { person_name: "John", age: "22.5" },
594
+ { person_name: "Dave", age: "18" },
595
+ { person_name: "Kile", age: "0" }
596
+ ]) # => false
597
+
598
+ mapper.errors.messages # =>
599
+ # { :"people.0.age" => ["must be an integer"],
600
+ # :"people.2.age" => ["must be greater than or equal to 1"] }
601
+ ```
602
+
603
+ ### Extensions
604
+
605
+ Aside from core functionality and behavior defined in this gem, there is also
606
+ [flatter-extensions](https://github.com/akuzko/flatter-extensions) gem that
607
+ provides extensions to help you use mappers more efficiently. At this point there
608
+ are following extensions:
609
+
610
+ - `:multiparam` Allows you to define multiparam mappings by adding `:multiparam`
611
+ option to mapping. Works pretty much like `Rails` multiparam attribute assignment.
612
+ - `:skipping` Allows to skip mappers (mountings) from the processing chain by
613
+ calling `skip!` method on a particular mapper. When used in before validation
614
+ callbacks, for example, allows you to ignore some extra processing.
615
+ - `:order` Allows you to manually control processing order of mappers and their
616
+ mountings. Provides `:index` option for mountings, which can be either a Number,
617
+ which means order for both validation and saving routines, or a hash like
618
+ `index: {validate: -1, save: 2}`. By default all mappers have index of `0` and
619
+ processed from top to bottom.
620
+ - `:active_record` Very useful extension that allows you to effectively use mappers
621
+ when working with ActiveRecord objects with defined relationships and associations
622
+ that form a structured graph that you want to work with as a plain data structure.
623
+
624
+ ### Public API
625
+
626
+ Some methods of the public API that should help you building your mappers:
627
+
628
+ #### Mapper methods
629
+
630
+ - `name` - return a mapper name.
631
+
632
+ - `target` - returns mapper target - an object mapper extracts values from
633
+ and assigns values to using defined mappings.
634
+
635
+ - `mappings` - returns a plain hash of all the mappings (including ones related
636
+ to mounted mappers) in a form of `{name <String> => mapping object <Mapping>}`.
637
+ Note that for empty collections there will be no mentions of item mappings at all.
638
+ If collection has only one item, it's mappings will be listed as the rest.
639
+ If there are multiple same-named mappings, they will be listed in array.
640
+
641
+ - `mapping_names` - returns a list of all **available** mappings. This differs
642
+ from `mappings.keys`, since `mapping_names` represents a list of all mappings
643
+ that may be used by mapper. Essentially, this is the list of mapper's
644
+ attribute accessor methods.
645
+
646
+ - `mapping(name)` - returns a mapping with a `name` name. The same as `mappings[name.to_s]`
647
+
648
+ - `mountings` - returns a plain hash of all mounted mappers (including all used traits)
649
+ in a form of `{name <String> => mapper object <Mapper>}`. Just like in case with
650
+ mappings, mountings with same name will be listed in array.
651
+
652
+ - `mounting_names` - returns a list of all **available** mountings. This represents
653
+ a list of reader methods that will return a sub-hash of specific mounting or
654
+ an array of such hashes for collections.
655
+
656
+ - `mounting(name)` - finds a mounting by name. Best used for addressing singular
657
+ mountings within a mapper, but also has other internal usages under the hood
658
+ (see sources of `Flatter::Mapper::AttributeMethods` module).
659
+
660
+ - `read` - returns a hash of all values obtained by all mappings in a form of
661
+ `{name <String> => value <Object>}`.
662
+
663
+ - `write(params)` - for each defined mapping, including mappings from mounted
664
+ mappers and traits, passes value from params that corresponds to mapping name
665
+ to that mapping's `write` method.
666
+
667
+ - `valid?` - runs validation routines and returns `true` if there are no errors.
668
+
669
+ - `errors` - returns mapper's `Errors` object.
670
+
671
+ - `save` - runs save routines. If target object responds to `save` method, will
672
+ call it and return it's value. Returns true otherwise. If multiple mappers
673
+ are mounted, returns `true` only if all mounted mappers returned `true` on saving
674
+ their targets.
675
+
676
+ - `apply(params)` - writes `params`, runs validation and runs save routines if
677
+ validation passed.
678
+
679
+ - `collection?` - returns `true` if mapper is a collection mapper.
680
+
681
+ - `trait?` - returns `true` if mapper is a trait mapper.
682
+
683
+ #### Mapping methods
684
+
685
+ - `name` - returns mapping name.
686
+
687
+ - `target_attribute` - returns an attribute name which mapping maps to.
688
+
689
+ - `read` - reads value from target according to setup.
690
+
691
+ - `read!` - tries to directly read value from target based on mapping's `target_attribute`
692
+ property. Ignores `:reader` option.
693
+
694
+ - `write(value)` - assigns a `value` to target according to setup.
695
+
696
+ - `write!(value)` - tries to directly assign a value to target based on mapping's
697
+ `target_attribute` property. Ignores `:writer` option.
25
698
 
26
699
  ## Development
27
700
 
28
- 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.
701
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
702
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
703
+ prompt that will allow you to experiment.
29
704
 
30
705
  ## Contributing
31
706
 
@@ -35,4 +710,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/akuzko
35
710
  ## License
36
711
 
37
712
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
38
-