flatter 0.1.0 → 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 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
-