active_interaction 4.1.0 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +150 -1
  3. data/CONTRIBUTING.md +11 -3
  4. data/README.md +256 -215
  5. data/lib/active_interaction/array_input.rb +77 -0
  6. data/lib/active_interaction/base.rb +14 -98
  7. data/lib/active_interaction/concerns/active_recordable.rb +3 -3
  8. data/lib/active_interaction/concerns/missable.rb +2 -2
  9. data/lib/active_interaction/errors.rb +6 -88
  10. data/lib/active_interaction/exceptions.rb +47 -0
  11. data/lib/active_interaction/filter/column.rb +59 -0
  12. data/lib/active_interaction/filter/error.rb +40 -0
  13. data/lib/active_interaction/filter.rb +40 -52
  14. data/lib/active_interaction/filters/abstract_date_time_filter.rb +9 -6
  15. data/lib/active_interaction/filters/abstract_numeric_filter.rb +7 -3
  16. data/lib/active_interaction/filters/array_filter.rb +40 -6
  17. data/lib/active_interaction/filters/boolean_filter.rb +4 -3
  18. data/lib/active_interaction/filters/date_filter.rb +1 -1
  19. data/lib/active_interaction/filters/date_time_filter.rb +1 -1
  20. data/lib/active_interaction/filters/decimal_filter.rb +1 -1
  21. data/lib/active_interaction/filters/float_filter.rb +1 -1
  22. data/lib/active_interaction/filters/hash_filter.rb +23 -15
  23. data/lib/active_interaction/filters/integer_filter.rb +1 -1
  24. data/lib/active_interaction/filters/interface_filter.rb +12 -12
  25. data/lib/active_interaction/filters/object_filter.rb +9 -3
  26. data/lib/active_interaction/filters/record_filter.rb +21 -11
  27. data/lib/active_interaction/filters/string_filter.rb +1 -1
  28. data/lib/active_interaction/filters/symbol_filter.rb +1 -1
  29. data/lib/active_interaction/filters/time_filter.rb +4 -4
  30. data/lib/active_interaction/hash_input.rb +43 -0
  31. data/lib/active_interaction/input.rb +23 -0
  32. data/lib/active_interaction/inputs.rb +161 -46
  33. data/lib/active_interaction/locale/en.yml +0 -1
  34. data/lib/active_interaction/locale/fr.yml +0 -1
  35. data/lib/active_interaction/locale/it.yml +0 -1
  36. data/lib/active_interaction/locale/ja.yml +0 -1
  37. data/lib/active_interaction/locale/pt-BR.yml +0 -1
  38. data/lib/active_interaction/modules/validation.rb +6 -17
  39. data/lib/active_interaction/version.rb +1 -1
  40. data/lib/active_interaction.rb +41 -36
  41. data/spec/active_interaction/array_input_spec.rb +166 -0
  42. data/spec/active_interaction/base_spec.rb +34 -248
  43. data/spec/active_interaction/concerns/active_modelable_spec.rb +3 -3
  44. data/spec/active_interaction/concerns/active_recordable_spec.rb +7 -7
  45. data/spec/active_interaction/concerns/hashable_spec.rb +8 -8
  46. data/spec/active_interaction/concerns/missable_spec.rb +9 -9
  47. data/spec/active_interaction/concerns/runnable_spec.rb +34 -32
  48. data/spec/active_interaction/errors_spec.rb +60 -43
  49. data/spec/active_interaction/{filter_column_spec.rb → filter/column_spec.rb} +3 -10
  50. data/spec/active_interaction/filter_spec.rb +27 -6
  51. data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +2 -2
  52. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +2 -2
  53. data/spec/active_interaction/filters/array_filter_spec.rb +109 -16
  54. data/spec/active_interaction/filters/boolean_filter_spec.rb +12 -11
  55. data/spec/active_interaction/filters/date_filter_spec.rb +32 -27
  56. data/spec/active_interaction/filters/date_time_filter_spec.rb +34 -29
  57. data/spec/active_interaction/filters/decimal_filter_spec.rb +20 -18
  58. data/spec/active_interaction/filters/file_filter_spec.rb +7 -7
  59. data/spec/active_interaction/filters/float_filter_spec.rb +19 -17
  60. data/spec/active_interaction/filters/hash_filter_spec.rb +16 -18
  61. data/spec/active_interaction/filters/integer_filter_spec.rb +24 -22
  62. data/spec/active_interaction/filters/interface_filter_spec.rb +105 -82
  63. data/spec/active_interaction/filters/object_filter_spec.rb +52 -36
  64. data/spec/active_interaction/filters/record_filter_spec.rb +61 -39
  65. data/spec/active_interaction/filters/string_filter_spec.rb +7 -7
  66. data/spec/active_interaction/filters/symbol_filter_spec.rb +6 -6
  67. data/spec/active_interaction/filters/time_filter_spec.rb +57 -34
  68. data/spec/active_interaction/hash_input_spec.rb +58 -0
  69. data/spec/active_interaction/i18n_spec.rb +22 -17
  70. data/spec/active_interaction/inputs_spec.rb +170 -18
  71. data/spec/active_interaction/integration/array_interaction_spec.rb +3 -7
  72. data/spec/active_interaction/integration/record_integration_spec.rb +5 -0
  73. data/spec/active_interaction/modules/validation_spec.rb +8 -31
  74. data/spec/spec_helper.rb +9 -0
  75. data/spec/support/concerns.rb +2 -2
  76. data/spec/support/filters.rb +27 -51
  77. data/spec/support/interactions.rb +4 -4
  78. metadata +43 -44
  79. data/lib/active_interaction/filter_column.rb +0 -57
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # [ActiveInteraction][]
2
2
 
3
3
  ActiveInteraction manages application-specific business logic.
4
- It's an implementation of the command pattern in Ruby.
4
+ It's an implementation of service objects designed to blend seamlessly into Rails.
5
5
 
6
6
  [![Version](https://img.shields.io/gem/v/active_interaction.svg?style=flat-square)](https://rubygems.org/gems/active_interaction)
7
7
  [![Test](https://img.shields.io/github/workflow/status/AaronLasseigne/active_interaction/Test?label=Test&style=flat-square)](https://github.com/AaronLasseigne/active_interaction/actions?query=workflow%3ATest)
@@ -17,23 +17,25 @@ handles your verbs.
17
17
  - [Basic usage](#basic-usage)
18
18
  - [Validations](#validations)
19
19
  - [Filters](#filters)
20
- - [Array](#array)
21
- - [Boolean](#boolean)
22
- - [File](#file)
23
- - [Hash](#hash)
24
- - [Interface](#interface)
25
- - [Object](#object)
26
- - [Record](#record)
27
- - [String](#string)
28
- - [Symbol](#symbol)
29
- - [Dates and times](#dates-and-times)
30
- - [Date](#date)
31
- - [DateTime](#datetime)
32
- - [Time](#time)
33
- - [Numbers](#numbers)
34
- - [Decimal](#decimal)
35
- - [Float](#float)
36
- - [Integer](#integer)
20
+ - [Basic Filters](#basic-filters)
21
+ - [Array](#array)
22
+ - [Boolean](#boolean)
23
+ - [File](#file)
24
+ - [Hash](#hash)
25
+ - [String](#string)
26
+ - [Symbol](#symbol)
27
+ - [Dates and times](#dates-and-times)
28
+ - [Date](#date)
29
+ - [DateTime](#datetime)
30
+ - [Time](#time)
31
+ - [Numbers](#numbers)
32
+ - [Decimal](#decimal)
33
+ - [Float](#float)
34
+ - [Integer](#integer)
35
+ - [Advanced Filters](#advanced-filters)
36
+ - [Interface](#interface)
37
+ - [Object](#object)
38
+ - [Record](#record)
37
39
  - [Rails](#rails)
38
40
  - [Setup](#setup)
39
41
  - [Controller](#controller)
@@ -51,7 +53,7 @@ handles your verbs.
51
53
  - [Descriptions](#descriptions)
52
54
  - [Errors](#errors)
53
55
  - [Forms](#forms)
54
- - [Grouped inputs](#grouped-inputs)
56
+ - [Shared input options](#shared-input-options)
55
57
  - [Optional inputs](#optional-inputs)
56
58
  - [Translations](#translations)
57
59
  - [Credits](#credits)
@@ -63,18 +65,17 @@ handles your verbs.
63
65
  Add it to your Gemfile:
64
66
 
65
67
  ``` rb
66
- gem 'active_interaction', '~> 4.1'
68
+ gem 'active_interaction', '~> 5.1'
67
69
  ```
68
70
 
69
71
  Or install it manually:
70
72
 
71
73
  ``` sh
72
- $ gem install active_interaction --version '~> 4.1'
74
+ $ gem install active_interaction --version '~> 5.1'
73
75
  ```
74
76
 
75
77
  This project uses [Semantic Versioning][]. Check out [GitHub releases][] for a
76
- detailed list of changes. For help upgrading to version 2, please read [the
77
- announcement post][].
78
+ detailed list of changes.
78
79
 
79
80
  ## Basic usage
80
81
 
@@ -89,7 +90,7 @@ you need to do two things:
89
90
  2. **Define your business logic.** Do this by implementing the `#execute`
90
91
  method. Each input you defined will be available as the type you specified.
91
92
  If any of the inputs are invalid, `#execute` won't be run. Filters are
92
- responsible for type checking your inputs. Check out [the validations
93
+ responsible for checking your inputs. Check out [the validations
93
94
  section](#validations) if you need more than that.
94
95
 
95
96
  That covers the basics. Let's put it all together into a simple example that
@@ -141,7 +142,7 @@ Square.run!(x: 2.1)
141
142
 
142
143
  ### Validations
143
144
 
144
- ActiveInteraction type checks your inputs. Often you'll want more than that.
145
+ ActiveInteraction checks your inputs. Often you'll want more than that.
145
146
  For instance, you may want an input to be a string with at least one
146
147
  non-whitespace character. Instead of writing your own validation for that, you
147
148
  can use validations from ActiveModel.
@@ -164,7 +165,7 @@ end
164
165
  ```
165
166
 
166
167
  When you run this interaction, two things will happen. **First
167
- ActiveInteraction will type check your inputs. Then ActiveModel will validate
168
+ ActiveInteraction will check your inputs. Then ActiveModel will validate
168
169
  them.** If both of those are happy, it will be executed.
169
170
 
170
171
  ``` rb
@@ -214,7 +215,9 @@ alternatives that can be reasonably coerced. Typically the coercions come from
214
215
  Rails, so `"1"` can be interpreted as the boolean value `true`, the string
215
216
  `"1"`, or the number `1`.
216
217
 
217
- ### Array
218
+ ### Basic Filters
219
+
220
+ #### Array
218
221
 
219
222
  In addition to accepting arrays, array inputs will convert
220
223
  `ActiveRecord::Relation`s into arrays.
@@ -261,7 +264,33 @@ array :managers do
261
264
  end
262
265
  ```
263
266
 
264
- ### Boolean
267
+ Errors that occur will be indexed based on the Rails configuration setting
268
+ `index_nested_attribute_errors`. You can also manually override this setting
269
+ with the `:index_errors` option. In this state is is possible to get multiple
270
+ errors from a single filter.
271
+
272
+ ```ruby
273
+ class ArrayInteraction < ActiveInteraction::Base
274
+ array :favorite_numbers, index_errors: true do
275
+ integer
276
+ end
277
+
278
+ def execute
279
+ favorite_numbers
280
+ end
281
+ end
282
+
283
+ ArrayInteraction.run(favorite_numbers: [8, 'bazillion']).errors.details
284
+ => {:"favorite_numbers[1]"=>[{:error=>:invalid_type, :type=>"array"}]}
285
+ ```
286
+
287
+ With `:index_errors` set to `false` the error would have been:
288
+
289
+ ```ruby
290
+ {:favorite_numbers=>[{:error=>:invalid_type, :type=>"array"}]}
291
+ ```
292
+
293
+ #### Boolean
265
294
 
266
295
  Boolean filters convert the strings `"1"`, `"true"`, and `"on"`
267
296
  (case-insensitive) into `true`. They also convert `"0"`, `"false"`, and `"off"`
@@ -282,7 +311,7 @@ BooleanInteraction.run!(kool_aid: true)
282
311
  # => "Oh yeah!"
283
312
  ```
284
313
 
285
- ### File
314
+ #### File
286
315
 
287
316
  File filters also accept `TempFile`s and anything that responds to `#rewind`.
288
317
  That means that you can pass the `params` from uploading files via forms in
@@ -303,7 +332,7 @@ FileInteraction.run!(readme: File.open('README.md'))
303
332
  # => 21563
304
333
  ```
305
334
 
306
- ### Hash
335
+ #### Hash
307
336
 
308
337
  Hash filters accept hashes. The expected value types are given by passing a
309
338
  block and nesting other filters. You can have any number of filters inside a
@@ -355,165 +384,7 @@ hash :stuff,
355
384
  strip: false
356
385
  ```
357
386
 
358
- ### Interface
359
-
360
- Interface filters allow you to specify an interface that the passed value must
361
- meet in order to pass. The name of the interface is used to look for a constant
362
- inside the ancestor listing for the passed value. This allows for a variety of
363
- checks depending on what's passed. Class instances are checked for an included
364
- module or an inherited ancestor class. Classes are checked for an extended
365
- module or an inherited ancestor class. Modules are checked for an extended
366
- module.
367
-
368
- ``` rb
369
- class InterfaceInteraction < ActiveInteraction::Base
370
- interface :exception
371
-
372
- def execute
373
- exception
374
- end
375
- end
376
-
377
- InterfaceInteraction.run!(exception: Exception)
378
- # ActiveInteraction::InvalidInteractionError: Exception is not a valid interface
379
- InterfaceInteraction.run!(exception: NameError) # a subclass of Exception
380
- # => NameError
381
- ```
382
-
383
- You can use `:from` to specify a class or module. This would be the equivalent
384
- of what's above.
385
-
386
- ```rb
387
- class InterfaceInteraction < ActiveInteraction::Base
388
- interface :error,
389
- from: Exception
390
-
391
- def execute
392
- error
393
- end
394
- end
395
- ```
396
-
397
- You can also create an anonymous interface on the fly by passing the `methods`
398
- option.
399
-
400
- ``` rb
401
- class InterfaceInteraction < ActiveInteraction::Base
402
- interface :serializer,
403
- methods: %i[dump load]
404
-
405
- def execute
406
- input = '{ "is_json" : true }'
407
- object = serializer.load(input)
408
- output = serializer.dump(object)
409
-
410
- output
411
- end
412
- end
413
-
414
- require 'json'
415
-
416
- InterfaceInteraction.run!(serializer: Object.new)
417
- # ActiveInteraction::InvalidInteractionError: Serializer is not a valid interface
418
- InterfaceInteraction.run!(serializer: JSON)
419
- # => "{\"is_json\":true}"
420
- ```
421
-
422
- ### Object
423
-
424
- Object filters allow you to require an instance of a particular class or one of
425
- its subclasses.
426
-
427
- ``` rb
428
- class Cow
429
- def moo
430
- 'Moo!'
431
- end
432
- end
433
-
434
- class ObjectInteraction < ActiveInteraction::Base
435
- object :cow
436
-
437
- def execute
438
- cow.moo
439
- end
440
- end
441
-
442
- ObjectInteraction.run!(cow: Object.new)
443
- # ActiveInteraction::InvalidInteractionError: Cow is not a valid object
444
- ObjectInteraction.run!(cow: Cow.new)
445
- # => "Moo!"
446
- ```
447
-
448
- The class name is automatically determined by the filter name. If your filter
449
- name is different than your class name, use the `class` option. It can be
450
- either the class, a string, or a symbol.
451
-
452
- ``` rb
453
- object :dolly1,
454
- class: Sheep
455
- object :dolly2,
456
- class: 'Sheep'
457
- object :dolly3,
458
- class: :Sheep
459
- ```
460
-
461
- If you have value objects or you would like to build one object from another,
462
- you can use the `converter` option. It is only called if the value provided is
463
- not an instance of the class or one of its subclasses. The `converter` option
464
- accepts a symbol that specifies a class method on the object class or a proc.
465
- Both will be passed the value and any errors thrown inside the converter will
466
- cause the value to be considered invalid. Any returned value that is not the
467
- correct class will also be treated as invalid. The value given to the `default`
468
- option will also be converted.
469
-
470
- ``` rb
471
- class ObjectInteraction < ActiveInteraction::Base
472
- object :ip_address,
473
- class: IPAddr,
474
- converter: :new
475
-
476
- def execute
477
- ip_address
478
- end
479
- end
480
-
481
- ObjectInteraction.run!(ip_address: '192.168.1.1')
482
- # #<IPAddr: IPv4:192.168.1.1/255.255.255.255>
483
-
484
- ObjectInteraction.run!(ip_address: 1)
485
- # ActiveInteraction::InvalidInteractionError: Ip address is not a valid object
486
- ```
487
-
488
- ### Record
489
-
490
- Record filters allow you to require an instance of a particular class (or one
491
- of its subclasses) or a value that can be used to locate an instance of the
492
- object. If the value does not match, it will call `find` on the class of the
493
- record. This is particularly useful when working with ActiveRecord objects.
494
- Like an object filter, the class is derived from the name passed but can be
495
- specified with the `class` option. The value given to the `default` option will
496
- also be found.
497
-
498
- ``` rb
499
- class RecordInteraction < ActiveInteraction::Base
500
- record :encoding
501
-
502
- def execute
503
- encoding
504
- end
505
- end
506
-
507
- > RecordInteraction.run!(encoding: Encoding::US_ASCII)
508
- => #<Encoding:US-ASCII>
509
-
510
- > RecordInteraction.run!(encoding: 'ascii')
511
- => #<Encoding:US-ASCII>
512
- ```
513
-
514
- A different method can be specified by providing a symbol to the `finder` option.
515
-
516
- ### String
387
+ #### String
517
388
 
518
389
  String filters define inputs that only accept strings.
519
390
 
@@ -540,7 +411,7 @@ string :comment,
540
411
  strip: false
541
412
  ```
542
413
 
543
- ### Symbol
414
+ #### Symbol
544
415
 
545
416
  Symbol filters define inputs that accept symbols. Strings will be converted
546
417
  into symbols.
@@ -560,7 +431,7 @@ SymbolInteraction.run!(method: :object_id)
560
431
  # => #<Proc:0x007fdc9ba94118>
561
432
  ```
562
433
 
563
- ### Dates and times
434
+ #### Dates and times
564
435
 
565
436
  Filters that work with dates and times behave similarly. By default, they all
566
437
  convert strings into their expected data types using `.parse`. Blank strings
@@ -568,7 +439,7 @@ will be treated as `nil`. If you give the `format` option, they will instead
568
439
  convert strings using `.strptime`. Note that formats won't work with `DateTime`
569
440
  and `Time` filters if a time zone is set.
570
441
 
571
- #### Date
442
+ ##### Date
572
443
 
573
444
  ``` rb
574
445
  class DateInteraction < ActiveInteraction::Base
@@ -590,7 +461,7 @@ date :birthday,
590
461
  format: '%Y-%m-%d'
591
462
  ```
592
463
 
593
- #### DateTime
464
+ ##### DateTime
594
465
 
595
466
  ``` rb
596
467
  class DateTimeInteraction < ActiveInteraction::Base
@@ -612,7 +483,7 @@ date_time :start,
612
483
  format: '%Y-%m-%dT%H:%M:%S'
613
484
  ```
614
485
 
615
- #### Time
486
+ ##### Time
616
487
 
617
488
  In addition to converting strings with `.parse` (or `.strptime`), time filters
618
489
  convert numbers with `.at`.
@@ -637,13 +508,13 @@ time :start,
637
508
  format: '%Y-%m-%dT%H:%M:%S'
638
509
  ```
639
510
 
640
- ### Numbers
511
+ #### Numbers
641
512
 
642
513
  All numeric filters accept numeric input. They will also convert strings using
643
514
  the appropriate method from `Kernel` (like `.Float`). Blank strings will be
644
515
  treated as `nil`.
645
516
 
646
- #### Decimal
517
+ ##### Decimal
647
518
 
648
519
  ``` rb
649
520
  class DecimalInteraction < ActiveInteraction::Base
@@ -667,7 +538,7 @@ decimal :dollars,
667
538
  digits: 2
668
539
  ```
669
540
 
670
- #### Float
541
+ ##### Float
671
542
 
672
543
  ``` rb
673
544
  class FloatInteraction < ActiveInteraction::Base
@@ -684,7 +555,7 @@ FloatInteraction.run!(x: 2.1)
684
555
  # => 4.41
685
556
  ```
686
557
 
687
- #### Integer
558
+ ##### Integer
688
559
 
689
560
  ``` rb
690
561
  class IntegerInteraction < ActiveInteraction::Base
@@ -725,6 +596,167 @@ IntegerInteraction.run!(limit1: "08", limit2: "08", limit3: "08")
725
596
  ActiveInteraction::InvalidInteractionError: Limit2 is not a valid integer, Limit3 is not a valid integer
726
597
  ```
727
598
 
599
+ ### Advanced Filters
600
+
601
+ #### Interface
602
+
603
+ Interface filters allow you to specify an interface that the passed value must
604
+ meet in order to pass. The name of the interface is used to look for a constant
605
+ inside the ancestor listing for the passed value. This allows for a variety of
606
+ checks depending on what's passed. Class instances are checked for an included
607
+ module or an inherited ancestor class. Classes are checked for an extended
608
+ module or an inherited ancestor class. Modules are checked for an extended
609
+ module.
610
+
611
+ ``` rb
612
+ class InterfaceInteraction < ActiveInteraction::Base
613
+ interface :exception
614
+
615
+ def execute
616
+ exception
617
+ end
618
+ end
619
+
620
+ InterfaceInteraction.run!(exception: Exception)
621
+ # ActiveInteraction::InvalidInteractionError: Exception is not a valid interface
622
+ InterfaceInteraction.run!(exception: NameError) # a subclass of Exception
623
+ # => NameError
624
+ ```
625
+
626
+ You can use `:from` to specify a class or module. This would be the equivalent
627
+ of what's above.
628
+
629
+ ```rb
630
+ class InterfaceInteraction < ActiveInteraction::Base
631
+ interface :error,
632
+ from: Exception
633
+
634
+ def execute
635
+ error
636
+ end
637
+ end
638
+ ```
639
+
640
+ You can also create an anonymous interface on the fly by passing the `methods`
641
+ option.
642
+
643
+ ``` rb
644
+ class InterfaceInteraction < ActiveInteraction::Base
645
+ interface :serializer,
646
+ methods: %i[dump load]
647
+
648
+ def execute
649
+ input = '{ "is_json" : true }'
650
+ object = serializer.load(input)
651
+ output = serializer.dump(object)
652
+
653
+ output
654
+ end
655
+ end
656
+
657
+ require 'json'
658
+
659
+ InterfaceInteraction.run!(serializer: Object.new)
660
+ # ActiveInteraction::InvalidInteractionError: Serializer is not a valid interface
661
+ InterfaceInteraction.run!(serializer: JSON)
662
+ # => "{\"is_json\":true}"
663
+ ```
664
+
665
+ #### Object
666
+
667
+ Object filters allow you to require an instance of a particular class or one of
668
+ its subclasses.
669
+
670
+ ``` rb
671
+ class Cow
672
+ def moo
673
+ 'Moo!'
674
+ end
675
+ end
676
+
677
+ class ObjectInteraction < ActiveInteraction::Base
678
+ object :cow
679
+
680
+ def execute
681
+ cow.moo
682
+ end
683
+ end
684
+
685
+ ObjectInteraction.run!(cow: Object.new)
686
+ # ActiveInteraction::InvalidInteractionError: Cow is not a valid object
687
+ ObjectInteraction.run!(cow: Cow.new)
688
+ # => "Moo!"
689
+ ```
690
+
691
+ The class name is automatically determined by the filter name. If your filter
692
+ name is different than your class name, use the `class` option. It can be
693
+ either the class, a string, or a symbol.
694
+
695
+ ``` rb
696
+ object :dolly1,
697
+ class: Sheep
698
+ object :dolly2,
699
+ class: 'Sheep'
700
+ object :dolly3,
701
+ class: :Sheep
702
+ ```
703
+
704
+ If you have value objects or you would like to build one object from another,
705
+ you can use the `converter` option. It is only called if the value provided is
706
+ not an instance of the class or one of its subclasses. The `converter` option
707
+ accepts a symbol that specifies a class method on the object class or a proc.
708
+ Both will be passed the value and any errors thrown inside the converter will
709
+ cause the value to be considered invalid. Any returned value that is not the
710
+ correct class will also be treated as invalid. Any `default` that is not an
711
+ instance of the class or subclass and is not `nil` will also be converted.
712
+
713
+ ``` rb
714
+ class ObjectInteraction < ActiveInteraction::Base
715
+ object :ip_address,
716
+ class: IPAddr,
717
+ converter: :new
718
+
719
+ def execute
720
+ ip_address
721
+ end
722
+ end
723
+
724
+ ObjectInteraction.run!(ip_address: '192.168.1.1')
725
+ # #<IPAddr: IPv4:192.168.1.1/255.255.255.255>
726
+
727
+ ObjectInteraction.run!(ip_address: 1)
728
+ # ActiveInteraction::InvalidInteractionError: Ip address is not a valid object
729
+ ```
730
+
731
+ #### Record
732
+
733
+ Record filters allow you to require an instance of a particular class (or one
734
+ of its subclasses) or a value that can be used to locate an instance of the
735
+ object. If the value does not match, it will call `find` on the class of the
736
+ record. This is particularly useful when working with ActiveRecord objects.
737
+ Like an object filter, the class is derived from the name passed but can be
738
+ specified with the `class` option. Any `default` that is not an instance of the
739
+ class or subclass and is not `nil` will also be found. Blank strings passed in
740
+ will be treated as `nil`.
741
+
742
+ ``` rb
743
+ class RecordInteraction < ActiveInteraction::Base
744
+ record :encoding
745
+
746
+ def execute
747
+ encoding
748
+ end
749
+ end
750
+
751
+ > RecordInteraction.run!(encoding: Encoding::US_ASCII)
752
+ => #<Encoding:US-ASCII>
753
+
754
+ > RecordInteraction.run!(encoding: 'ascii')
755
+ => #<Encoding:US-ASCII>
756
+ ```
757
+
758
+ A different method can be specified by providing a symbol to the `finder` option.
759
+
728
760
  ## Rails
729
761
 
730
762
  ActiveInteraction plays nicely with Rails. You can use interactions to handle
@@ -736,10 +768,7 @@ resourceful actions.
736
768
 
737
769
  We recommend putting your interactions in `app/interactions`. It's also very
738
770
  helpful to group them by model. That way you can look in
739
- `app/interactions/accounts` for all the ways you can interact with accounts. In
740
- order to use this structure add
741
- `config.autoload_paths += Dir.glob("#{config.root}/app/interactions/*")` in
742
- your `application.rb`
771
+ `app/interactions/accounts` for all the ways you can interact with accounts.
743
772
 
744
773
  ```
745
774
  - app/
@@ -842,7 +871,7 @@ end
842
871
  ```
843
872
 
844
873
  Note that it's perfectly fine to add errors during execution. Not all errors
845
- have to come from type checking or validation.
874
+ have to come from checking or validation.
846
875
 
847
876
  #### New
848
877
 
@@ -1025,7 +1054,7 @@ interaction's lifecycle.
1025
1054
 
1026
1055
  ``` rb
1027
1056
  class Increment < ActiveInteraction::Base
1028
- set_callback :type_check, :before, -> { puts 'before type check' }
1057
+ set_callback :filter, :before, -> { puts 'before filter' }
1029
1058
 
1030
1059
  integer :x
1031
1060
 
@@ -1047,7 +1076,7 @@ class Increment < ActiveInteraction::Base
1047
1076
  end
1048
1077
 
1049
1078
  Increment.run!(x: 1)
1050
- # before type check
1079
+ # before filter
1051
1080
  # after validate
1052
1081
  # >>>
1053
1082
  # executing
@@ -1055,7 +1084,7 @@ Increment.run!(x: 1)
1055
1084
  # => 2
1056
1085
  ```
1057
1086
 
1058
- In order, the available callbacks are `type_check`, `validate`, and `execute`.
1087
+ In order, the available callbacks are `filter`, `validate`, and `execute`.
1059
1088
  You can set `before`, `after`, or `around` on any of them.
1060
1089
 
1061
1090
  ### Composition
@@ -1178,7 +1207,7 @@ errors.
1178
1207
  ``` rb
1179
1208
  outcome = BuyItem.run(item: 'Thing', options: { gift_wrapped: 'yes' })
1180
1209
  outcome.errors.messages
1181
- # => {:credit_card=>["is required"], :item=>["is not a valid object"], :options=>["has an invalid nested value (\"gift_wrapped\" => \"yes\")"]}
1210
+ # => {:credit_card=>["is required"], :item=>["is not a valid object"], :"options.gift_wrapped"=>["is not a valid boolean"]}
1182
1211
  ```
1183
1212
 
1184
1213
  Determining the type of error based on the string is difficult if not
@@ -1187,7 +1216,7 @@ the same list of errors with a testable label representing the error.
1187
1216
 
1188
1217
  ``` rb
1189
1218
  outcome.errors.details
1190
- # => {:credit_card=>[{:error=>:missing}], :item=>[{:type=>"object", :error=>:invalid_type}], :options=>[{:name=>"\"gift_wrapped\"", :value=>"\"yes\"", :error=>:invalid_nested}]}
1219
+ # => {:credit_card=>[{:error=>:missing}], :item=>[{:error=>:invalid_type, :type=>"object"}], :"options.gift_wrapped"=>[{:error=>:invalid_type, :type=>"boolean"}]}
1191
1220
  ```
1192
1221
 
1193
1222
  Detailed errors can also be manually added during the execute call by passing a
@@ -1337,7 +1366,7 @@ used to define the inputs on your interaction will relay type information to
1337
1366
  these gems. As a result, form fields will automatically use the appropriate
1338
1367
  input type.
1339
1368
 
1340
- ### Grouped inputs
1369
+ ### Shared input options
1341
1370
 
1342
1371
  It can be convenient to apply the same options to a bunch of inputs. One common
1343
1372
  use case is making many inputs optional. Instead of setting `default: nil` on
@@ -1358,8 +1387,8 @@ Optional inputs can be defined by using the `:default` option as described in
1358
1387
  are merged to create `inputs`. There are times where it is useful to know
1359
1388
  whether a value was passed to `run` or the result of a filter default. In
1360
1389
  particular, it is useful when `nil` is an acceptable value. For example, you
1361
- may optionally track your users' birthdays. You can use the `given?` predicate
1362
- to see if an input was even passed to `run`. With `given?` you can also check
1390
+ may optionally track your users' birthdays. You can use the `inputs.given?` predicate
1391
+ to see if an input was even passed to `run`. With `inputs.given?` you can also check
1363
1392
  the input of a hash or array filter by passing a series of keys or indexes to
1364
1393
  check.
1365
1394
 
@@ -1370,7 +1399,7 @@ class UpdateUser < ActiveInteraction::Base
1370
1399
  default: nil
1371
1400
 
1372
1401
  def execute
1373
- user.birthday = birthday if given?(:birthday)
1402
+ user.birthday = birthday if inputs.given?(:birthday)
1374
1403
  errors.merge!(user.errors) unless user.save
1375
1404
  user
1376
1405
  end
@@ -1424,7 +1453,6 @@ hsilgne:
1424
1453
  errors:
1425
1454
  messages:
1426
1455
  invalid: dilavni si
1427
- invalid_nested: (%{value} <= %{name}) eulav detsen dilavni na sah
1428
1456
  invalid_type: '%{type} dilav a ton si'
1429
1457
  missing: deriuqer si
1430
1458
  ```
@@ -1444,6 +1472,19 @@ I18nInteraction.run(name: false).errors.messages[:name]
1444
1472
  # => ["gnirts dilav a ton si"]
1445
1473
  ```
1446
1474
 
1475
+ Everything else works like an `activerecord` entry. For example, to rename an
1476
+ attribute you can use `attributes`.
1477
+
1478
+ Here we'll rename the `num` attribute on an interaction named `product`:
1479
+
1480
+ ``` yml
1481
+ en:
1482
+ active_interaction:
1483
+ attributes:
1484
+ product:
1485
+ num: 'Number'
1486
+ ```
1487
+
1447
1488
  ## Credits
1448
1489
 
1449
1490
  ActiveInteraction is brought to you by [Aaron Lasseigne][].
@@ -1457,13 +1498,13 @@ ActiveInteraction is licensed under [the MIT License][].
1457
1498
 
1458
1499
  [activeinteraction]: https://github.com/AaronLasseigne/active_interaction
1459
1500
  [API Documentation]: http://rubydoc.info/github/AaronLasseigne/active_interaction
1460
- [semantic versioning]: http://semver.org/spec/v2.0.0.html
1501
+ [Semantic Versioning]: http://semver.org/spec/v2.0.0.html
1461
1502
  [GitHub releases]: https://github.com/AaronLasseigne/active_interaction/releases
1462
1503
  [aaron lasseigne]: https://github.com/AaronLasseigne
1463
1504
  [taylor fausak]: https://github.com/tfausak
1464
1505
  [our contribution guidelines]: CONTRIBUTING.md
1465
1506
  [complete list of contributors]: https://github.com/AaronLasseigne/active_interaction/graphs/contributors
1466
- [the mit license]: LICENSE.md
1507
+ [the MIT License]: LICENSE.md
1467
1508
  [formtastic]: https://rubygems.org/gems/formtastic
1468
1509
  [simple_form]: https://rubygems.org/gems/simple_form
1469
1510
  [the filters section]: #filters