form_obj 0.5.0 → 1.0.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 +5 -5
- data/.gitignore +0 -4
- data/CHANGELOG.md +43 -1
- data/Gemfile.lock +2 -17
- data/README.md +768 -213
- data/Rakefile +1 -4
- data/bundle_update.sh +27 -0
- data/form_obj.gemspec +0 -1
- data/lib/form_obj/form/array.rb +34 -10
- data/lib/form_obj/form.rb +4 -0
- data/lib/form_obj/model_mapper/array.rb +52 -33
- data/lib/form_obj/model_mapper/attribute.rb +13 -2
- data/lib/form_obj/model_mapper/model_attribute/item.rb +1 -0
- data/lib/form_obj/model_mapper/model_attribute.rb +4 -3
- data/lib/form_obj/model_mapper.rb +9 -1
- data/lib/form_obj/struct/array.rb +35 -24
- data/lib/form_obj/struct/attribute.rb +7 -3
- data/lib/form_obj/struct.rb +16 -10
- data/lib/form_obj/version.rb +1 -1
- data/run_tests.sh +0 -1
- metadata +4 -18
- data/.rspec +0 -3
data/README.md
CHANGED
@@ -15,6 +15,8 @@ ActiveModel: 3.2+
|
|
15
15
|
The gem is tested against all ruby versions and all versions of its dependencies
|
16
16
|
except ActiveSupport and ActiveModel version 4.0.x because they requires Minitest 4 which is not compatible with Minitest 5.
|
17
17
|
|
18
|
+
This gem follows [Semantic Versioning](https://semver.org/).
|
19
|
+
|
18
20
|
## Installation
|
19
21
|
|
20
22
|
Add this line to your application's Gemfile:
|
@@ -33,8 +35,6 @@ Or install it yourself as:
|
|
33
35
|
|
34
36
|
## Usage
|
35
37
|
|
36
|
-
**WARNING!!!** The gem is still under development. Expect braking changes in `FormObj::ModelMapper` module.<br/>
|
37
|
-
|
38
38
|
Class `FormObj::Struct` allows to describe complicated data structure, to update it with `update_attributes` method and to get its hash representation with `to_hash` method.
|
39
39
|
|
40
40
|
Class `FormObj::Form` inherits from `FormObj::Struct` and adds form builder compatibility and includes ActiveModel validations.
|
@@ -62,15 +62,38 @@ model attributes name) and
|
|
62
62
|
1. [`load_from_model` - Initialize Form Object from Model](#31-load_from_model---initialize-form-object-from-model)
|
63
63
|
2. [`load_from_models` - Initialize Form Object from Few Models](#32-load_from_models---initialize-form-object-from-few-models)
|
64
64
|
3. [Do Not Map Certain Attribute](#33-do-not-map-certain-attribute)
|
65
|
-
4. [Map
|
66
|
-
5. [Map
|
67
|
-
6. [
|
68
|
-
7. [
|
69
|
-
|
70
|
-
|
71
|
-
|
65
|
+
4. [Do Not Map Certain Attribute For Reading From Model](#34-do-not-map-certain-attribute-for-reading-from-model)
|
66
|
+
5. [Do Not Map Certain Attribute For Writing to Model](#35-do-not-map-certain-attribute-for-writing-to-model)
|
67
|
+
6. [Map Nested Form Objects](#36-map-nested-form-objects)
|
68
|
+
7. [Map Nested Form Object to Parent Level Model](#37-map-nested-form-object-to-parent-level-model)
|
69
|
+
8. [Map Nested Form Objects to A Hash Model](#38-map-nested-form-object-to-a-hash-model)
|
70
|
+
9. [Map Array of Nested Form Objects](#39-map-array-of-nested-form-objects)
|
71
|
+
10. [Map Array of Nested Form Objects to Nested Array of Nested Models](#310-map-array-of-nested-form-objects-to-nested-array-of-nested-models)
|
72
|
+
11. [Default Implementation of Loading of Array of Models](#311-default-implementation-of-loading-of-array-of-models)
|
73
|
+
12. [Custom Implementation of Loading of Array of Models](#312-custom-implementation-of-loading-of-array-of-models)
|
74
|
+
13. [Sync Form Object to Model(s)](#313-sync-form-object-to-models)
|
75
|
+
14. [Sync Array of Nested Form Objects to Model(s)](#314-sync-array-of-nested-form-objects-to-models)
|
76
|
+
15. [Sync Array of Nested Form Objects to `ActiveRecord`-like Models](#315-sync-array-of-nested-form-objects-to-activerecord-like-models)
|
77
|
+
16. [Customize Sync to Array of Models](#316-customize-sync-to-array-of-models)
|
78
|
+
17. [Model Validation and Persistence](#317-model-validation-and-persistence)
|
79
|
+
18. [Copy Model Validation Errors into Form Object](#318-copy-model-validation-errors-into-form-object)
|
80
|
+
19. [Serialize Form Object to Model Hash](#319-serialize-form-object-to-model-hash)
|
72
81
|
4. [Rails Example](#4-rails-example)
|
73
|
-
5. [Reference Guide](#5-reference-guide-
|
82
|
+
5. [Reference Guide: `attribute`'s paremeters](#5-reference-guide-attributes-parameters)
|
83
|
+
1. [`FormObj::Struct`](#51-formobjstruct)
|
84
|
+
1. [Parameter `array`](#511-parameter-array)
|
85
|
+
2. [Parameter `class`](#512-parameter-class)
|
86
|
+
3. [Parameter `default`](#513-parameter-default)
|
87
|
+
4. [Parameter `primary_key`](#514-parameter-primary_key)
|
88
|
+
2. [`FormObj::Form`](#52-formobjform)
|
89
|
+
3. [`FormObj::Form` with included `FormObj::ModelMapper`](#53-formobjform-with-included-formobjmodelmapper)
|
90
|
+
1. [Parameter `model`](#531-parameter-model)
|
91
|
+
2. [Parameter `model_attribute`](#532-parameter-model_attribute)
|
92
|
+
3. [Parameter `model_class`](#533-parameter-model_class)
|
93
|
+
4. [Parameter `model_hash`](#534-parameter-model_hash)
|
94
|
+
5. [Parameter `model_nesting`](#535-parameter-model_nesting)
|
95
|
+
6. [Parameter `read_from_model`](#536-parameter-read_from_model)
|
96
|
+
7. [Parameter `write_to_model`](#537-parameter-write_to_model)
|
74
97
|
|
75
98
|
### 1. `FormObj::Struct`
|
76
99
|
|
@@ -320,7 +343,7 @@ class Team < FormObj::Struct
|
|
320
343
|
attribute :car, class: Car, default: 36
|
321
344
|
end
|
322
345
|
|
323
|
-
Team.new # => FormObj::
|
346
|
+
Team.new # => FormObj::WrongDefaultValueClassError: FormObj::WrongDefaultValueClassError
|
324
347
|
```
|
325
348
|
|
326
349
|
#### 1.2. Array of `FormObj::Struct`
|
@@ -640,7 +663,7 @@ class Team < FormObj::Struct
|
|
640
663
|
attribute :cars, class: Car, array: true, default: [36]
|
641
664
|
end
|
642
665
|
|
643
|
-
Team.new # => FormObj::
|
666
|
+
Team.new # => FormObj::WrongDefaultValueClassError: FormObj::WrongDefaultValueClassError
|
644
667
|
```
|
645
668
|
|
646
669
|
#### 1.3. Serialize `FormObj::Struct` to Hash
|
@@ -801,6 +824,7 @@ team.cars # => [#< code: 1, driver: "Ascari">]
|
|
801
824
|
|
802
825
|
In oppose to this `FormObj::Form` `update_attributes` method ignores elements that are absent in the hash but
|
803
826
|
marks for destruction those elements that has `_destroy: true` key in the hash.
|
827
|
+
New elements with `_destroy: true` are not created at all.
|
804
828
|
|
805
829
|
```ruby
|
806
830
|
class Team < FormObj::Form
|
@@ -821,7 +845,9 @@ team.cars[1].code # => 2
|
|
821
845
|
team.cars[1].driver # => 'James Hunt'
|
822
846
|
team.cars[1].marked_for_destruction? # => false
|
823
847
|
|
824
|
-
team.update_attributes(cars: [{code: 1, _destroy: true}])
|
848
|
+
team.update_attributes(cars: [{code: 1, _destroy: true}, {code: 3, _destroy: true}])
|
849
|
+
|
850
|
+
team.cars.size # => 2
|
825
851
|
|
826
852
|
team.cars[0].code # => 2
|
827
853
|
team.cars[0].driver # => 'James Hunt'
|
@@ -894,7 +920,8 @@ Use dot notation to map model attribute to a nested model. Use colon to specify
|
|
894
920
|
|
895
921
|
#### 3.1. `load_from_model` - Initialize Form Object from Model
|
896
922
|
|
897
|
-
Use `load_from_model(model)` method to initialize form object from the model.
|
923
|
+
Use `load_from_model(model)` method to initialize form object from the model.
|
924
|
+
This method available both as class method and as instance method.
|
898
925
|
|
899
926
|
```ruby
|
900
927
|
class Team < FormObj::Form
|
@@ -908,12 +935,14 @@ end
|
|
908
935
|
car_model = { engine: Struct.new(:power).new(335) }
|
909
936
|
team_model = Struct.new(:team_name, :year, :car).new('Ferrari', 1950, car_model)
|
910
937
|
|
911
|
-
team = Team.
|
912
|
-
team.to_hash
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
938
|
+
team = Team.load_from_model(team_model)
|
939
|
+
team.to_hash # => {
|
940
|
+
# => :name => "Ferrari"
|
941
|
+
# => :year => 1950
|
942
|
+
# => :engine_power => 335
|
943
|
+
# => }
|
944
|
+
|
945
|
+
team.load_from_model(team_model)
|
917
946
|
```
|
918
947
|
|
919
948
|
So attributes are mapped as follows:
|
@@ -924,10 +953,10 @@ So attributes are mapped as follows:
|
|
924
953
|
| `team.year` | `team_model.year` |
|
925
954
|
| `team.engine_power` | `team_model.car[:engine].power` |
|
926
955
|
|
927
|
-
|
928
956
|
#### 3.2. `load_from_models` - Initialize Form Object from Few Models
|
929
957
|
|
930
958
|
Use `load_from_models(models)` method to initialize form object from few models.
|
959
|
+
This method available both as class method and as instance method.
|
931
960
|
`models` parameter is a hash where keys are the name of models and values are models themselves.
|
932
961
|
|
933
962
|
By default each form object attribute is mapped to `:default` model.
|
@@ -945,12 +974,14 @@ end
|
|
945
974
|
car_model = { engine: Struct.new(:power).new(335) }
|
946
975
|
team_model = Struct.new(:team_name, :year).new('Ferrari', 1950) # <- doesn't have car attribute !!!
|
947
976
|
|
948
|
-
team = Team.
|
949
|
-
team.to_hash
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
977
|
+
team = Team.load_from_models(default: team_model, car: car_model)
|
978
|
+
team.to_hash # => {
|
979
|
+
# => :name => "Ferrari"
|
980
|
+
# => :year => 1950
|
981
|
+
# => :engine_power => 335
|
982
|
+
# => }
|
983
|
+
|
984
|
+
team.load_from_models(default: team_model, car: car_model)
|
954
985
|
```
|
955
986
|
|
956
987
|
So attributes are mapped as follows:
|
@@ -976,12 +1007,12 @@ end
|
|
976
1007
|
|
977
1008
|
team_model = Struct.new(:team_name, :year, :engine_power).new('Ferrari', 1950, 335)
|
978
1009
|
|
979
|
-
team = Team.
|
980
|
-
team.to_hash
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
1010
|
+
team = Team.load_from_model(team_model)
|
1011
|
+
team.to_hash # => {
|
1012
|
+
# => :name => "Ferrari"
|
1013
|
+
# => :year => 1950
|
1014
|
+
# => :engine_power => nil
|
1015
|
+
# => }
|
985
1016
|
```
|
986
1017
|
|
987
1018
|
So attributes are mapped as follows:
|
@@ -992,7 +1023,53 @@ So attributes are mapped as follows:
|
|
992
1023
|
| `form.year` | `team_model.year` |
|
993
1024
|
| `form.engine_power` | - |
|
994
1025
|
|
995
|
-
|
1026
|
+
It also works for other methods: `sync_to_model(s)`, `to_model(s)_hash`, `copy_errors_from_model(s)`.
|
1027
|
+
|
1028
|
+
#### 3.4. Do Not Map Certain Attribute For Reading From Model
|
1029
|
+
|
1030
|
+
Use `read_from_model: false` in order to avoid mapping of the attribute only in `load_from_model(s)` methods.
|
1031
|
+
|
1032
|
+
```ruby
|
1033
|
+
class Team < FormObj::Form
|
1034
|
+
include FormObj::ModelMapper
|
1035
|
+
|
1036
|
+
attribute :name
|
1037
|
+
attribute :year, read_from_model: false
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
team_model = Struct.new(:name, :year).new('Ferrari', 1950)
|
1041
|
+
team = Team.new(name: 'McLaren', year: 1966)
|
1042
|
+
|
1043
|
+
team.load_from_model(team_model)
|
1044
|
+
|
1045
|
+
team.name # => "Ferrari"
|
1046
|
+
team.year # => 1966
|
1047
|
+
```
|
1048
|
+
|
1049
|
+
#### 3.5. Do Not Map Certain Attribute For Writing to Model
|
1050
|
+
|
1051
|
+
Use `write_to_model: false` in order to avoid mapping of the attribute only in `sync_to_model(s)`, `to_model(s)_hash`, `copy_errors_from_model(s)` methods.
|
1052
|
+
|
1053
|
+
```ruby
|
1054
|
+
class Team < FormObj::Form
|
1055
|
+
include FormObj::ModelMapper
|
1056
|
+
|
1057
|
+
attribute :name
|
1058
|
+
attribute :year, write_to_model: false
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
team_model = Struct.new(:name, :year).new('Ferrari', 1950)
|
1062
|
+
team = Team.new(name: 'McLaren', year: 1966)
|
1063
|
+
|
1064
|
+
team.sync_to_model(team_model)
|
1065
|
+
|
1066
|
+
team_model.name # => "McLaren"
|
1067
|
+
team_model.year # => 1950
|
1068
|
+
|
1069
|
+
team.to_model_hash # => {:name => "McLaren"}
|
1070
|
+
```
|
1071
|
+
|
1072
|
+
#### 3.6. Map Nested Form Objects
|
996
1073
|
|
997
1074
|
Nested forms are mapped by default to corresponding nested models.
|
998
1075
|
|
@@ -1011,15 +1088,15 @@ end
|
|
1011
1088
|
car_model = Struct.new(:code, :driver).new('340 F1', 'Ascari')
|
1012
1089
|
team_model = Struct.new(:team_name, :year, :car).new('Ferrari', 1950, car_model)
|
1013
1090
|
|
1014
|
-
team = Team.
|
1015
|
-
team.to_hash
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1091
|
+
team = Team.load_from_model(team_model)
|
1092
|
+
team.to_hash # => {
|
1093
|
+
# => :name => "Ferrari",
|
1094
|
+
# => :year => 1950,
|
1095
|
+
# => :car => {
|
1096
|
+
# => :code => "340 F1",
|
1097
|
+
# => :driver => "Ascari"
|
1098
|
+
# => }
|
1099
|
+
# => }
|
1023
1100
|
```
|
1024
1101
|
|
1025
1102
|
So attributes are mapped as follows:
|
@@ -1031,7 +1108,11 @@ So attributes are mapped as follows:
|
|
1031
1108
|
| `team.car.code` | `team_model.car.code` |
|
1032
1109
|
| `team.car.driver` | `team_model.car.driver` |
|
1033
1110
|
|
1034
|
-
|
1111
|
+
It also works for other methods: `sync_to_model(s)` and `to_model(s)_hash`
|
1112
|
+
|
1113
|
+
#### 3.7. Map Nested Form Object to Parent Level Model
|
1114
|
+
|
1115
|
+
Use `model_nesting: false` parameter to map nested form object to parent level model.
|
1035
1116
|
|
1036
1117
|
```ruby
|
1037
1118
|
class NestedForm < FormObj::Form
|
@@ -1047,15 +1128,15 @@ end
|
|
1047
1128
|
|
1048
1129
|
team_model = Struct.new(:team_name, :year, :code, :driver).new('Ferrari', 1950, '340 F1', 'Ascari')
|
1049
1130
|
|
1050
|
-
team = Team.
|
1051
|
-
team.to_hash
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1131
|
+
team = Team.load_from_model(team_model)
|
1132
|
+
team.to_hash # => {
|
1133
|
+
# => :name => "Ferrari",
|
1134
|
+
# => :year => 1950,
|
1135
|
+
# => :car => {
|
1136
|
+
# => :code => "340 F1",
|
1137
|
+
# => :driver => "Ascari"
|
1138
|
+
# => }
|
1139
|
+
# => }
|
1059
1140
|
```
|
1060
1141
|
|
1061
1142
|
So attributes are mapped as follows:
|
@@ -1067,7 +1148,9 @@ So attributes are mapped as follows:
|
|
1067
1148
|
| `team.car.code` | `team_model.code` |
|
1068
1149
|
| `team.car.driver` | `team_model.driver` |
|
1069
1150
|
|
1070
|
-
|
1151
|
+
It also works for other methods: `sync_to_model(s)` and `to_model(s)_hash`
|
1152
|
+
|
1153
|
+
#### 3.8. Map Nested Form Object to A Hash Model
|
1071
1154
|
|
1072
1155
|
Use `model_hash: true` in order to map a nested form object to a hash as a model.
|
1073
1156
|
|
@@ -1086,15 +1169,15 @@ end
|
|
1086
1169
|
car_model = { code: '340 F1', driver: 'Ascari' }
|
1087
1170
|
team_model = Struct.new(:team_name, :year, :car).new('Ferrari', 1950, car_model)
|
1088
1171
|
|
1089
|
-
team = Team.
|
1090
|
-
team.to_hash
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1172
|
+
team = Team.load_from_model(team_model)
|
1173
|
+
team.to_hash # => {
|
1174
|
+
# => :name => "Ferrari",
|
1175
|
+
# => :year => 1950,
|
1176
|
+
# => :car => {
|
1177
|
+
# => :code => "340 F1",
|
1178
|
+
# => :driver => "Ascari"
|
1179
|
+
# => }
|
1180
|
+
# => }
|
1098
1181
|
```
|
1099
1182
|
|
1100
1183
|
So attributes are mapped as follows:
|
@@ -1106,7 +1189,44 @@ So attributes are mapped as follows:
|
|
1106
1189
|
| `team.car.code` | `team_model.car[:code]` |
|
1107
1190
|
| `team.car.driver` | `team_model.car[:driver]` |
|
1108
1191
|
|
1109
|
-
|
1192
|
+
It also works for other methods: `sync_to_model(s)` and `to_model(s)_hash`
|
1193
|
+
|
1194
|
+
#### 3.9. Map Array of Nested Form Objects
|
1195
|
+
|
1196
|
+
Array of nested forms is mapped by default to corresponding array (or for example to `ActiveRecord::Relation` in case of Rails) of nested models.
|
1197
|
+
|
1198
|
+
```ruby
|
1199
|
+
class Team < FormObj::Form
|
1200
|
+
include FormObj::ModelMapper
|
1201
|
+
|
1202
|
+
attribute :name
|
1203
|
+
attribute :year
|
1204
|
+
attribute :cars, array: true, model_class: CarModel do
|
1205
|
+
attribute :code, primary_key: true
|
1206
|
+
attribute :driver
|
1207
|
+
end
|
1208
|
+
end
|
1209
|
+
```
|
1210
|
+
|
1211
|
+
#### 3.10. Map Array of Nested Form Objects to Nested Array of Nested Models
|
1212
|
+
|
1213
|
+
If corresponding `:model_attribute` parameter uses dot notations to reference
|
1214
|
+
nested models the value of `:model_class` parameter should be an array of corresponding model classes.
|
1215
|
+
|
1216
|
+
```ruby
|
1217
|
+
class ArrayForm < FormObj::Form
|
1218
|
+
include FormObj::ModelMapper
|
1219
|
+
|
1220
|
+
attribute :name
|
1221
|
+
attribute :year
|
1222
|
+
attribute :cars, array: true, model_attribute: 'equipment.cars', model_class: [Equipment, CarModel] do
|
1223
|
+
attribute :code, primary_key: true
|
1224
|
+
attribute :driver
|
1225
|
+
end
|
1226
|
+
end
|
1227
|
+
```
|
1228
|
+
|
1229
|
+
#### 3.11. Default Implementation of Loading of Array of Models
|
1110
1230
|
|
1111
1231
|
By default `load_from_model(s)` methods loads all models from arrays.
|
1112
1232
|
|
@@ -1133,27 +1253,29 @@ cars_model = [CarModel.new('340 F1', 'Ascari'), CarModel.new('275 F1', 'Villores
|
|
1133
1253
|
colours_model = [ColourModel.new(:red, 0xFF0000), ColourModel.new(:white, 0xFFFFFF)]
|
1134
1254
|
team_model = Struct.new(:team_name, :year, :cars, :colours).new('Ferrari', 1950, cars_model, colours_model)
|
1135
1255
|
|
1136
|
-
team = Team.
|
1137
|
-
team.to_hash
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1256
|
+
team = Team.load_from_model(team_model)
|
1257
|
+
team.to_hash # => {
|
1258
|
+
# => :name => "Ferrari",
|
1259
|
+
# => :year => 1950,
|
1260
|
+
# => :cars => [{
|
1261
|
+
# => :code => "340 F1",
|
1262
|
+
# => :driver => "Ascari"
|
1263
|
+
# => }, {
|
1264
|
+
# => :code => "275 F1",
|
1265
|
+
# => :driver => "Villoresi"
|
1266
|
+
# => }],
|
1267
|
+
# => :colours => [{
|
1268
|
+
# => :name => :red,
|
1269
|
+
# => :rgb => 0xFF0000
|
1270
|
+
# => }, {
|
1271
|
+
# => :name => :white,
|
1272
|
+
# => :rgb => 0xFFFFFF
|
1273
|
+
# => }]
|
1274
|
+
# => }
|
1155
1275
|
```
|
1156
1276
|
|
1277
|
+
#### 3.12. Custom Implementation of Loading of Array of Models
|
1278
|
+
|
1157
1279
|
`FormObj::ModelMapper::Array` class implements method (where `*args` are additional params passed to `load_from_model(s)` methods)
|
1158
1280
|
|
1159
1281
|
```ruby
|
@@ -1204,39 +1326,39 @@ cars_model = [CarModel.new('340 F1', 'Ascari'), CarModel.new('275 F1', 'Villores
|
|
1204
1326
|
colours_model = [ColourModel.new(:red, 0xFF0000), ColourModel.new(:white, 0xFFFFFF)]
|
1205
1327
|
team_model = Struct.new(:team_name, :year, :cars, :colours).new('Ferrari', 1950, cars_model, colours_model)
|
1206
1328
|
|
1207
|
-
team = Team.
|
1208
|
-
team.to_hash
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
team = Team.
|
1225
|
-
team.to_hash
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1329
|
+
team = Team.load_from_model(team_model, offset: 0, limit: 1)
|
1330
|
+
team.to_hash # => {
|
1331
|
+
# => :name => "Ferrari",
|
1332
|
+
# => :year => 1950,
|
1333
|
+
# => :cars => [{
|
1334
|
+
# => :code => "340 F1",
|
1335
|
+
# => :driver => "Ascari"
|
1336
|
+
# => }],
|
1337
|
+
# => :colours => [{
|
1338
|
+
# => :name => :red,
|
1339
|
+
# => :rgb => 0xFF0000
|
1340
|
+
# => }, {
|
1341
|
+
# => :name => :white,
|
1342
|
+
# => :rgb => 0xFFFFFF
|
1343
|
+
# => }]
|
1344
|
+
# => }
|
1345
|
+
|
1346
|
+
team = Team.load_from_model(team_model, offset: 1, limit: 1)
|
1347
|
+
team.to_hash # => {
|
1348
|
+
# => :name => "Ferrari",
|
1349
|
+
# => :year => 1950,
|
1350
|
+
# => :cars => [{
|
1351
|
+
# => :code => "275 F1",
|
1352
|
+
# => :driver => "Villoresi"
|
1353
|
+
# => }],
|
1354
|
+
# => :colours => [{
|
1355
|
+
# => :name => :red,
|
1356
|
+
# => :rgb => 0xFF0000
|
1357
|
+
# => }, {
|
1358
|
+
# => :name => :white,
|
1359
|
+
# => :rgb => 0xFFFFFF
|
1360
|
+
# => }]
|
1361
|
+
# => }
|
1240
1362
|
```
|
1241
1363
|
|
1242
1364
|
Note that our new implementation of `iterate_through_models_to_load_them` limits only cars but not colours.
|
@@ -1244,7 +1366,7 @@ It identifies requested model attribute using `model_attribute.names` which retu
|
|
1244
1366
|
an array of model attribute accessors (in our example `[:cars]`)
|
1245
1367
|
|
1246
1368
|
In case of `ActiveRecord` model `iterate_through_models_to_load_them` will receive an instance of `ActiveRecord::Relation` as `models` parameter.
|
1247
|
-
This allows to load in the memory only necessary
|
1369
|
+
This allows to load in the memory only necessary associated models.
|
1248
1370
|
|
1249
1371
|
```ruby
|
1250
1372
|
class ArrayLoadLimit < FormObj::ModelMapper::Array
|
@@ -1257,13 +1379,13 @@ class ArrayLoadLimit < FormObj::ModelMapper::Array
|
|
1257
1379
|
end
|
1258
1380
|
```
|
1259
1381
|
|
1260
|
-
#### 3.
|
1382
|
+
#### 3.13. Sync Form Object to Model(s)
|
1261
1383
|
|
1262
1384
|
Use `sync_to_models(models)` to sync form object attributes to mapped models.
|
1263
|
-
Method returns self so
|
1385
|
+
Method returns self so calls could be chained.
|
1264
1386
|
|
1265
1387
|
```ruby
|
1266
|
-
class
|
1388
|
+
class Team < FormObj::Form
|
1267
1389
|
include FormObj::ModelMapper
|
1268
1390
|
|
1269
1391
|
attribute :name, model_attribute: :team_name
|
@@ -1274,66 +1396,196 @@ end
|
|
1274
1396
|
default_model = Struct.new(:team_name, :year).new('Ferrari', 1950)
|
1275
1397
|
car_model = { engine: Struct.new(:power).new(335) }
|
1276
1398
|
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1399
|
+
team = Team.new
|
1400
|
+
team.update_attributes(name: 'McLaren', year: 1966, engine_power: 415)
|
1401
|
+
team.sync_to_models(default: default_model, car: car_model)
|
1280
1402
|
|
1281
|
-
default_model.
|
1282
|
-
default_model.year
|
1283
|
-
car_model[:engine].power
|
1403
|
+
default_model.team_name # => "McLaren"
|
1404
|
+
default_model.year # => 1966
|
1405
|
+
car_model[:engine].power # => 415
|
1284
1406
|
```
|
1285
1407
|
|
1286
|
-
Use `
|
1408
|
+
Use `sync_to_model(model)` if form object is mapped to single model.
|
1287
1409
|
|
1288
|
-
|
1289
|
-
Also they don't call `valid?` method on the model(s).
|
1290
|
-
Instead they just assign form object attributes value to mapped model attributes
|
1291
|
-
using `<attribute_name>=` accessors on the model(s).
|
1292
|
-
|
1293
|
-
It is completely up to developer to do any additional validations on the model(s) and save it(them).
|
1410
|
+
#### 3.14. Sync Array of Nested Form Objects to Model(s)
|
1294
1411
|
|
1295
|
-
|
1296
|
-
|
1297
|
-
Saving array of form objects to corresponding array of models requires the class of the model to be known by the form object
|
1298
|
-
because it could create new instances of the model array elements.
|
1299
|
-
Use `:model_class` parameter to specify it.
|
1300
|
-
Form object will try to guess the name of the class from the name of the attribute if this parameter is absent.
|
1412
|
+
By default `FormObj::Form` with included `FormObj::ModelMapper` will try to match Form Objects and Models by primary key.
|
1413
|
+
Therefore Form Object primary key attribute has to be mapped to top level Model attribute.
|
1301
1414
|
|
1302
1415
|
```ruby
|
1303
|
-
|
1416
|
+
TeamModel = Struct.new(:cars)
|
1417
|
+
CarModel = Struct.new(:code, :driver)
|
1418
|
+
|
1419
|
+
class Team < FormObj::Form
|
1304
1420
|
include FormObj::ModelMapper
|
1305
1421
|
|
1306
|
-
attribute :
|
1307
|
-
|
1308
|
-
attribute :cars, array: true, model_class: Car do
|
1309
|
-
attribute :code, primary_key: true # <- primary key is specified on attribute level
|
1422
|
+
attribute :cars, array: true, model_class: CarModel do
|
1423
|
+
attribute :code, primary_key: true
|
1310
1424
|
attribute :driver
|
1311
1425
|
end
|
1312
1426
|
end
|
1313
|
-
```
|
1427
|
+
```
|
1314
1428
|
|
1315
|
-
|
1316
|
-
nested models the value of `:model_class` parameter should be an array of corresponding model classes.
|
1429
|
+
Attributes of successfully matched models will be updated from corresponding form objects.
|
1317
1430
|
|
1318
1431
|
```ruby
|
1319
|
-
|
1432
|
+
team_model = TeamModel.new([CarModel.new('275 F1', 'Ascari')])
|
1433
|
+
team = Team.new(cars: [{ code: '275 F1', driver: 'Villoresi' }])
|
1434
|
+
|
1435
|
+
team_model.cars # => [#<struct CarModel code="275 F1", driver="Ascari">]
|
1436
|
+
team.sync_to_model(team_model)
|
1437
|
+
team_model.cars # => [#<struct CarModel code="275 F1", driver="Villoresi">]
|
1438
|
+
```
|
1439
|
+
|
1440
|
+
New models will be created for form object that doesn't have corresponding models.
|
1441
|
+
In order to create a new model the model class has to be known.
|
1442
|
+
It can be specified by `:model_class` parameter.
|
1443
|
+
Otherwise form object will try to guess it from the attribute name.
|
1444
|
+
|
1445
|
+
```ruby
|
1446
|
+
team_model = TeamModel.new([])
|
1447
|
+
team = Team.new(cars: [{ code: '275 F1', driver: 'Villoresi' }])
|
1448
|
+
|
1449
|
+
team_model.cars # => []
|
1450
|
+
team.sync_to_model(team_model)
|
1451
|
+
team_model.cars # => [#<struct CarModel code="275 F1", driver="Villoresi">]
|
1452
|
+
```
|
1453
|
+
|
1454
|
+
Models that does not have corresponding objects will stay without changes.
|
1455
|
+
|
1456
|
+
```ruby
|
1457
|
+
team_model = TeamModel.new([CarModel.new('275 F1', 'Ascari')])
|
1458
|
+
team = Team.new(cars: [{ code: '275 F1', driver: 'Villoresi' }, { code: '340 F1', driver: 'Hunt' }])
|
1459
|
+
|
1460
|
+
team_model.cars # => [#<struct CarModel code="275 F1", driver="Ascari">]
|
1461
|
+
team.sync_to_model(team_model)
|
1462
|
+
team_model.cars # => [#<struct CarModel code="275 F1", driver="Villoresi">, #<struct CarModel code="340 F1", driver="Hunt">]
|
1463
|
+
```
|
1464
|
+
|
1465
|
+
If array does not respond to `:where` models that correspond to form objects marked for destruction will be destroyed.
|
1466
|
+
|
1467
|
+
```ruby
|
1468
|
+
team_model = TeamModel.new([CarModel.new('275 F1', 'Ascari')])
|
1469
|
+
team = Team.load_from_model(team_model) # => #<Team cars: [#< code: "275 F1", driver: nil>]>
|
1470
|
+
team.update_attributes(cars: [{ code: '275 F1', _destroy: true }]) # => #<Team cars: [#< code: "275 F1", driver: nil marked_for_destruction>]>
|
1471
|
+
|
1472
|
+
team_model.cars # => [#<struct CarModel code="275 F1", driver="Ascari">]
|
1473
|
+
team.sync_to_model(team_model)
|
1474
|
+
team_model.cars # => []
|
1475
|
+
```
|
1476
|
+
|
1477
|
+
#### 3.15. Sync Array of Nested Form Objects to `ActiveRecord`-like Models
|
1478
|
+
|
1479
|
+
if array respond to `:where` (aka `ActiveRecord`) models that correspond to form objects marked for destruction will be also marked for destruction.
|
1480
|
+
|
1481
|
+
```ruby
|
1482
|
+
class TeamModel < ApplicationRecord
|
1483
|
+
has_many :cars
|
1484
|
+
end
|
1485
|
+
|
1486
|
+
class Team < FormObj::Form
|
1320
1487
|
include FormObj::ModelMapper
|
1321
1488
|
|
1322
|
-
attribute :
|
1323
|
-
|
1324
|
-
|
1325
|
-
attribute :code, primary_key: true # <- primary key is specified on attribute level
|
1489
|
+
attribute :cars, array: true, model_class: CarModel do
|
1490
|
+
attribute :id
|
1491
|
+
attribute :code
|
1326
1492
|
attribute :driver
|
1327
1493
|
end
|
1328
1494
|
end
|
1495
|
+
|
1496
|
+
team_model = TeamModel.find(1)
|
1497
|
+
team_model.cars # => #<ActiveRecord::Associations::CollectionProxy [#<Car id: 1, code: "275 F1", driver: "Ascari">]>
|
1498
|
+
team_model.cars.first.marked_for_destruction? # => false
|
1499
|
+
|
1500
|
+
team = Team.load_from_model(team_model).update_attributes(cars: [{ id: 1, _destroy: true }])
|
1501
|
+
team.sync_to_model(team_model)
|
1502
|
+
|
1503
|
+
team_model.cars # => #<ActiveRecord::Associations::CollectionProxy [#<Car id: 1, code: "275 F1", driver: "Ascari">]>
|
1504
|
+
team_model.cars.first.marked_for_destruction? # => true
|
1505
|
+
```
|
1506
|
+
|
1507
|
+
#### 3.16. Customize Sync to Array of Models
|
1508
|
+
|
1509
|
+
`FormObj::ModelMapper::Array` has private methods: `sync_creation_to_models`, `sync_update_to_models`, `sync_destruction_to_models`.
|
1510
|
+
They are called during syncing process and could be overwritten.
|
1511
|
+
The new descendant of `FormObj::ModelMapper::Array` class has to be returned from `array_class` class method.
|
1512
|
+
As well as the `FormObj::Form` descendant class itself has to be returned from `nested_class` class method.
|
1513
|
+
|
1514
|
+
```ruby
|
1515
|
+
class MyForm < FormObj::Form
|
1516
|
+
class Array < FormObj::ModelMapper::Array
|
1517
|
+
private
|
1518
|
+
|
1519
|
+
def sync_destruction_to_models(models, ids_to_destroy)
|
1520
|
+
if models[:default].respond_to? :where
|
1521
|
+
models[:default].where(model_primary_key.name => ids_to_destroy).each { |model| puts "Mark for deletion model #{model}" }
|
1522
|
+
else
|
1523
|
+
models[:default].select { |model| ids_to_destroy.include? model_primary_key.read_from_model(model) }.each { |model| puts "Delete model #{model}" }
|
1524
|
+
end
|
1525
|
+
super
|
1526
|
+
end
|
1527
|
+
|
1528
|
+
def sync_update_to_models(models, items_to_update)
|
1529
|
+
items_to_update.each_pair do |model, form_object|
|
1530
|
+
puts "Update model #{model} with #{form_object.to_model_hash}"
|
1531
|
+
end
|
1532
|
+
super
|
1533
|
+
end
|
1534
|
+
|
1535
|
+
def sync_creation_to_models(models, form_objects_to_create)
|
1536
|
+
form_objects_to_create.each do |form_object|
|
1537
|
+
puts "Create model from #{form_object.to_model_hash}"
|
1538
|
+
end
|
1539
|
+
super
|
1540
|
+
end
|
1541
|
+
end
|
1542
|
+
|
1543
|
+
include FormObj::ModelMapper
|
1544
|
+
|
1545
|
+
def self.array_class
|
1546
|
+
Array
|
1547
|
+
end
|
1548
|
+
|
1549
|
+
def self.nested_class
|
1550
|
+
MyForm
|
1551
|
+
end
|
1552
|
+
end
|
1553
|
+
```
|
1554
|
+
|
1555
|
+
#### 3.17. Model Validation and Persistence
|
1556
|
+
|
1557
|
+
`sync_to_model(s)` do not call `save` method on the model(s).
|
1558
|
+
Also they don't call `valid?` method on the model(s).
|
1559
|
+
Instead they just assign form object attributes value to mapped model attributes
|
1560
|
+
using `<attribute_name>=` accessors on the model(s).
|
1561
|
+
|
1562
|
+
It is completely up to developer to do any additional validations on the model(s) and save it(them).
|
1563
|
+
|
1564
|
+
#### 3.18. Copy Model Validation Errors into Form Object
|
1565
|
+
|
1566
|
+
Even though validation could and should happen in the form object it is possible to have (additional) validation(s) in the model(s).
|
1567
|
+
In this case it is handy to copy model validation errors to form object in order to be able to present them to the user in a standard way.
|
1568
|
+
|
1569
|
+
Use `copy_errors_from_models(models)` or `copy_errors_from_model(model)` in order to do it.
|
1570
|
+
Methods return self so one can chain calls.
|
1571
|
+
|
1572
|
+
```ruby
|
1573
|
+
team.copy_errors_from_models(default: default_model, car: car_model)
|
1329
1574
|
```
|
1330
1575
|
|
1331
|
-
|
1576
|
+
In case of single model:
|
1577
|
+
```ruby
|
1578
|
+
team.copy_errors_from_model(model)
|
1579
|
+
```
|
1580
|
+
|
1581
|
+
For the moment `copy_errors_from_model(s)` do not support nested form object/model and array of nested form objects/models.
|
1582
|
+
|
1583
|
+
#### 3.19. Serialize Form Object to Model Hash
|
1332
1584
|
|
1333
1585
|
Use `to_model_hash(model = :default)` to get hash representation of the model that mapped to the form object.
|
1334
1586
|
|
1335
1587
|
```ruby
|
1336
|
-
class
|
1588
|
+
class Team < FormObj::Form
|
1337
1589
|
include FormObj::ModelMapper
|
1338
1590
|
|
1339
1591
|
attribute :name, model_attribute: :team_name
|
@@ -1341,39 +1593,37 @@ class MultiForm < FormObj::Form
|
|
1341
1593
|
attribute :engine_power, model: :car, model_attribute: ':engine.power'
|
1342
1594
|
end
|
1343
1595
|
|
1344
|
-
|
1345
|
-
multi_form.update_attributes(name: 'McLaren', year: 1966, engine_power: 415)
|
1596
|
+
team = Team.new(name: 'McLaren', year: 1966, engine_power: 415)
|
1346
1597
|
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1598
|
+
team.to_model_hash # => { :team_name => "McLaren", :year => 1966 }
|
1599
|
+
team.to_model_hash(:default) # => { :team_name => "McLaren", :year => 1966 }
|
1600
|
+
team.to_model_hash(:car) # => { :engine => { :power => 415 } }
|
1350
1601
|
```
|
1351
1602
|
|
1352
1603
|
Use `to_models_hash()` to get hash representation of all models that mapped to the form object.
|
1353
1604
|
|
1354
1605
|
```ruby
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1606
|
+
team.to_models_hash # => {
|
1607
|
+
# => default: { :team_name => "McLaren", :year => 1966 }
|
1608
|
+
# => car: { :engine => { :power => 415 } }
|
1609
|
+
# => }
|
1359
1610
|
```
|
1360
1611
|
|
1361
|
-
If array of form objects mapped to the parent model (`
|
1612
|
+
If array of form objects mapped to the parent model (`model_nesting: false`) it is serialized to `:self` key.
|
1362
1613
|
|
1363
1614
|
```ruby
|
1364
|
-
class
|
1615
|
+
class Team < FormObj::Form
|
1365
1616
|
include FormObj::ModelMapper
|
1366
1617
|
|
1367
1618
|
attribute :name
|
1368
1619
|
attribute :year
|
1369
|
-
attribute :cars, array: true,
|
1620
|
+
attribute :cars, array: true, model_nesting: false do
|
1370
1621
|
attribute :code, primary_key: true
|
1371
1622
|
attribute :driver
|
1372
1623
|
end
|
1373
1624
|
end
|
1374
1625
|
|
1375
|
-
|
1376
|
-
array_form.update_attributes(
|
1626
|
+
team = Team.new(
|
1377
1627
|
name: 'McLaren',
|
1378
1628
|
year: 1966,
|
1379
1629
|
cars: [{
|
@@ -1385,34 +1635,17 @@ array_form.update_attributes(
|
|
1385
1635
|
}]
|
1386
1636
|
)
|
1387
1637
|
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
```
|
1400
|
-
|
1401
|
-
#### 3.9. Copy Model Validation Errors into Form Object
|
1402
|
-
|
1403
|
-
Even though validation could and should happen in the form object it is possible to have (additional) validation(s) in the model(s).
|
1404
|
-
In this case it is handy to copy model validation errors to form object in order to be able to present them to the user in a standard way.
|
1405
|
-
|
1406
|
-
Use `copy_errors_from_models(models)` or `copy_errors_from_model(model)` in order to do it.
|
1407
|
-
Methods return self so one can chain calls.
|
1408
|
-
|
1409
|
-
```ruby
|
1410
|
-
multi_form.copy_errors_from_models(default: default_model, car: car_model)
|
1411
|
-
```
|
1412
|
-
|
1413
|
-
In case of single model:
|
1414
|
-
```ruby
|
1415
|
-
single_form.copy_errors_from_model(model)
|
1638
|
+
team.to_model_hash # => {
|
1639
|
+
# => :team_name => "McLaren",
|
1640
|
+
# => :year => 1966,
|
1641
|
+
# => :self => {
|
1642
|
+
# => :code => "M2B",
|
1643
|
+
# => :driver => "Bruce McLaren"
|
1644
|
+
# => }, {
|
1645
|
+
# => :code => "M7A",
|
1646
|
+
# => :driver => "Denis Hulme"
|
1647
|
+
# => }
|
1648
|
+
# => }
|
1416
1649
|
```
|
1417
1650
|
|
1418
1651
|
### 4. Rails Example
|
@@ -1556,29 +1789,351 @@ end
|
|
1556
1789
|
<% end %>
|
1557
1790
|
```
|
1558
1791
|
|
1559
|
-
### 5. Reference Guide: `attribute` parameters
|
1792
|
+
### 5. Reference Guide: `attribute`'s parameters
|
1793
|
+
|
1794
|
+
#### 5.1 `FormObj::Struct`
|
1795
|
+
|
1796
|
+
##### 5.1.1. Parameter `array`
|
1797
|
+
|
1798
|
+
*Default value:* `false`
|
1799
|
+
|
1800
|
+
Specifies attribute as an array of nested `FormObj::Struct`.
|
1801
|
+
The attribute shuld have either a block which describes the structure of array item
|
1802
|
+
or `class` parameter which refers to another `FormObj::Struct` which describes the structure of array item.
|
1803
|
+
|
1804
|
+
```ruby
|
1805
|
+
class Team < FormObj::Struct
|
1806
|
+
attribute :cars, array: true do
|
1807
|
+
attribute :id
|
1808
|
+
attribute :driver
|
1809
|
+
end
|
1810
|
+
end
|
1811
|
+
```
|
1812
|
+
|
1813
|
+
##### 5.1.2. Parameter `class`
|
1814
|
+
|
1815
|
+
Specifies the class of nested `FormObj::Struct`. Cannot be used if there is block definition of nested structure.
|
1816
|
+
Could be either class constant itself or the name of the class.
|
1817
|
+
|
1818
|
+
```ruby
|
1819
|
+
class Car < FormObj::Struct
|
1820
|
+
attribute :id
|
1821
|
+
attribute :driver
|
1822
|
+
end
|
1823
|
+
class Team < FormObj::Struct
|
1824
|
+
attribute :cars, array: true, class: Car
|
1825
|
+
end
|
1826
|
+
```
|
1827
|
+
|
1828
|
+
##### 5.1.3. Parameter `default`
|
1829
|
+
|
1830
|
+
Specifies the default value of an attribute.
|
1831
|
+
For nested `FormObj::Struct` could be specified either by its instance or by its hash representation.
|
1832
|
+
|
1833
|
+
```ruby
|
1834
|
+
class Team < FormObj::Struct
|
1835
|
+
attribute :name, default: 'Ferrari'
|
1836
|
+
attribute :cars, array: true, default: [{ id: 1, driver: 'Ascari' }] do
|
1837
|
+
attribute :id
|
1838
|
+
attribute :driver
|
1839
|
+
end
|
1840
|
+
end
|
1841
|
+
```
|
1842
|
+
|
1843
|
+
or
|
1844
|
+
|
1845
|
+
```ruby
|
1846
|
+
class Car < FormObj::Struct
|
1847
|
+
attribute :id
|
1848
|
+
attribute :driver
|
1849
|
+
end
|
1850
|
+
class Team < FormObj::Struct
|
1851
|
+
attribute :name, default: 'Ferrari'
|
1852
|
+
attribute :cars, array: true, class: 'Car', default: [Car.new(id: 1, driver: 'Ascari')]
|
1853
|
+
end
|
1854
|
+
```
|
1855
|
+
|
1856
|
+
##### 5.1.4. Parameter `primary_key`
|
1857
|
+
|
1858
|
+
*Default value:* `:id`
|
1859
|
+
|
1860
|
+
Specifies the primary key of nested `FormObj::Struct` for the array attribute.
|
1861
|
+
Could be specified either on the primary key attribute itself (`primary_key: true`)
|
1862
|
+
|
1863
|
+
```ruby
|
1864
|
+
class Team < FormObj::Struct
|
1865
|
+
attribute :cars, array: true do
|
1866
|
+
attribute :code, primary_key: true
|
1867
|
+
attribute :driver
|
1868
|
+
end
|
1869
|
+
end
|
1870
|
+
```
|
1871
|
+
|
1872
|
+
or on the array attribute.
|
1873
|
+
In latter case the value of the parameter should the name of the primary key attribute (e.g. `primary_key: :team_name`).
|
1874
|
+
|
1875
|
+
```ruby
|
1876
|
+
class Team < FormObj::Struct
|
1877
|
+
attribute :cars, array: true, primary_key: :code do
|
1878
|
+
attribute :code
|
1879
|
+
attribute :driver
|
1880
|
+
end
|
1881
|
+
end
|
1882
|
+
```
|
1883
|
+
|
1884
|
+
If both ways are mixed, than parameter specified on the array attribute will take precedence.
|
1885
|
+
|
1886
|
+
Composite primary key is not supported.
|
1887
|
+
|
1888
|
+
#### 5.2. `FormObj::Form`
|
1889
|
+
|
1890
|
+
All `FormObj::Struct` parameters can be used with `FormObj::Form`
|
1891
|
+
|
1892
|
+
#### 5.3. `FormObj::Form` with included `FormObj::ModelMapper`
|
1893
|
+
|
1894
|
+
All `FormObj::Form` parameters can be together with following.
|
1895
|
+
|
1896
|
+
##### 5.3.1. Parameter `model`
|
1897
|
+
|
1898
|
+
*Default value:* `:default`
|
1899
|
+
|
1900
|
+
Specifies the name of the model which this attribute is mapped on to.
|
1901
|
+
By default each attribute is mapped on to the `:default` model.
|
1902
|
+
|
1903
|
+
```ruby
|
1904
|
+
class TeamForm < FormObj::Form
|
1905
|
+
include FormObj::ModelMapper
|
1906
|
+
|
1907
|
+
attribute :name
|
1908
|
+
attribute :engine, model: :car
|
1909
|
+
end
|
1910
|
+
|
1911
|
+
Car = Struct.new(:engine)
|
1912
|
+
Team = Struct.new(:name)
|
1913
|
+
|
1914
|
+
team = Team.new('McLaren')
|
1915
|
+
car = Car.new('Ford')
|
1916
|
+
|
1917
|
+
team.name # => "McLaren"
|
1918
|
+
car.engine # => "Ford"
|
1919
|
+
team_form = TeamForm.load_from_models(default: team, car: car)
|
1920
|
+
team_form.name # => "McLaren"
|
1921
|
+
team_form.engine # => "Ford"
|
1922
|
+
```
|
1923
|
+
|
1924
|
+
##### 5.3.2. Parameter `model_attribute`
|
1925
|
+
|
1926
|
+
*Default value:* `<attribute name>`
|
1927
|
+
|
1928
|
+
Specifies the name of the model attribute which this attribute is mapped on.
|
1929
|
+
It supports dot-notation for mapping on the nested model attribute.
|
1930
|
+
|
1931
|
+
```ruby
|
1932
|
+
class TeamForm < FormObj::Form
|
1933
|
+
include FormObj::ModelMapper
|
1934
|
+
|
1935
|
+
attribute :car_power, model_attribute: 'car.engine_power'
|
1936
|
+
end
|
1937
|
+
|
1938
|
+
Car = Struct.new(:engine_power)
|
1939
|
+
Team = Struct.new(:car)
|
1940
|
+
|
1941
|
+
team = Team.new(Car.new(350))
|
1942
|
+
team.car.engine_power # => 350
|
1943
|
+
team_form = TeamForm.load_from_model(team)
|
1944
|
+
team_form.car_power # => 350
|
1945
|
+
```
|
1946
|
+
|
1947
|
+
Colon has to be used in front of corresponding `model_attribute` element if the nested model is a hash.
|
1948
|
+
|
1949
|
+
```ruby
|
1950
|
+
class TeamForm < FormObj::Form
|
1951
|
+
include FormObj::ModelMapper
|
1952
|
+
|
1953
|
+
attribute :car_power, model_attribute: 'car.:engine_power'
|
1954
|
+
end
|
1955
|
+
|
1956
|
+
Team = Struct.new(:car)
|
1957
|
+
|
1958
|
+
team = Team.new(engine_power: 350)
|
1959
|
+
team.car[:engine_power] # => 350
|
1960
|
+
team_form = TeamForm.load_from_model(team)
|
1961
|
+
team_form.car_power # => 350
|
1962
|
+
```
|
1963
|
+
|
1964
|
+
##### 5.3.3. Parameter `model_class`
|
1965
|
+
|
1966
|
+
*Default value:* `<attribute name>.to_s.classify`
|
1967
|
+
|
1968
|
+
This parameter can be used only for nested form objects.
|
1969
|
+
Specifies the class of the model which the nested form object is mapped on.
|
1970
|
+
|
1971
|
+
```ruby
|
1972
|
+
class TeamForm < FormObj::Form
|
1973
|
+
include FormObj::ModelMapper
|
1974
|
+
|
1975
|
+
attribute :name
|
1976
|
+
attribute :engine, model: :car
|
1977
|
+
end
|
1978
|
+
|
1979
|
+
Car = Struct.new(:engine)
|
1980
|
+
Team = Struct.new(:name)
|
1981
|
+
|
1982
|
+
team = Team.new('McLaren')
|
1983
|
+
car = Car.new('Ford')
|
1984
|
+
team.name # => "McLaren"
|
1985
|
+
car.engine # => "Ford"
|
1986
|
+
team_form = TeamForm.load_from_models(default: team, car: car)
|
1987
|
+
team_form.name # => "McLaren"
|
1988
|
+
team_form.engine # => "Ford"
|
1989
|
+
```
|
1990
|
+
|
1991
|
+
##### 5.3.4. Parameter `model_hash`
|
1992
|
+
|
1993
|
+
*Default value:* `false`
|
1994
|
+
|
1995
|
+
If nested model is hash it could be specified by means of this parameter.
|
1996
|
+
|
1997
|
+
```ruby
|
1998
|
+
class TeamForm < FormObj::Form
|
1999
|
+
include FormObj::ModelMapper
|
2000
|
+
|
2001
|
+
attribute :car, model_hash: true do
|
2002
|
+
attribute :power
|
2003
|
+
end
|
2004
|
+
end
|
2005
|
+
|
2006
|
+
Team = Struct.new(:car)
|
2007
|
+
|
2008
|
+
team = Team.new(power: 350)
|
2009
|
+
team.car[:power] # => 350
|
2010
|
+
team_form = TeamForm.load_from_model(team)
|
2011
|
+
team_form.car.power # => 350
|
2012
|
+
```
|
2013
|
+
|
2014
|
+
The same result could be achieved by using `:`-notation in `model_attribute` parameter.
|
2015
|
+
|
2016
|
+
```ruby
|
2017
|
+
class TeamForm < FormObj::Form
|
2018
|
+
include FormObj::ModelMapper
|
2019
|
+
|
2020
|
+
attribute :car do
|
2021
|
+
attribute :power, model_attribute: ':power'
|
2022
|
+
end
|
2023
|
+
end
|
2024
|
+
|
2025
|
+
Team = Struct.new(:car)
|
2026
|
+
|
2027
|
+
team = Team.new(power: 350)
|
2028
|
+
team.car[:power] # => 350
|
2029
|
+
team_form = TeamForm.load_from_model(team)
|
2030
|
+
team_form.car.power # => 350
|
2031
|
+
```
|
2032
|
+
|
2033
|
+
##### 5.3.5. Parameter `model_nesting`
|
2034
|
+
|
2035
|
+
*Default value:* `true`
|
2036
|
+
|
2037
|
+
This parameter can be used only for nested form objects.
|
2038
|
+
By default nested form object is mapped to nested model.
|
2039
|
+
If this parameter has value `false` the nested form object will be mapped to the same model as the parent form object is.
|
2040
|
+
|
2041
|
+
```ruby
|
2042
|
+
class TeamForm < FormObj::Form
|
2043
|
+
include FormObj::ModelMapper
|
2044
|
+
|
2045
|
+
attribute :car, model_nesting: false do
|
2046
|
+
attribute :power
|
2047
|
+
end
|
2048
|
+
end
|
2049
|
+
|
2050
|
+
Team = Struct.new(:power)
|
2051
|
+
|
2052
|
+
team = Team.new(350)
|
2053
|
+
team.power # => 350
|
2054
|
+
team_form = TeamForm.load_from_model(team)
|
2055
|
+
team_form.car.power # => 350
|
2056
|
+
```
|
2057
|
+
|
2058
|
+
Compare with example where `model_nesting: true`.
|
2059
|
+
|
2060
|
+
```ruby
|
2061
|
+
class TeamForm < FormObj::Form
|
2062
|
+
include FormObj::ModelMapper
|
2063
|
+
|
2064
|
+
attribute :car, model_nesting: true do
|
2065
|
+
attribute :power
|
2066
|
+
end
|
2067
|
+
end
|
2068
|
+
|
2069
|
+
Car = Struct.new(:power)
|
2070
|
+
Team = Struct.new(:car)
|
2071
|
+
|
2072
|
+
team = Team.new(Car.new(350))
|
2073
|
+
team.car.power # => 350
|
2074
|
+
team_form = TeamForm.load_from_model(team)
|
2075
|
+
team_form.car.power # => 350
|
2076
|
+
```
|
2077
|
+
|
2078
|
+
`model_nesting: true` can be omitted since it is its default value.
|
1560
2079
|
|
1561
|
-
|
1562
|
-
| --- |:---:|:---:|:---:| --- |
|
1563
|
-
| array | block* or `:class`** | `false` | `FormObj::Struct` | This attribute is an array of form objects. The structure of array element form object is described either in the block or in the separate class referenced by `:class` parameter |
|
1564
|
-
| class | - | - | `FormObj::Struct` | This attribute is either nested form object or array of form objects. The value of this parameter is the class of this form object or the name of the class. |
|
1565
|
-
| default | - | - | `FormObj::Struct` | Defines default value for the attribute. Nested structures default value can be defined either with Hash or with object. |
|
1566
|
-
| model_hash | block* or `:class`** | `false` | `FormObj::ModelMapper` | This attribute is either nested form object or array of form objects. This form object is mapped to a model of the class `Hash` so all its attributes should be accessed by `[:<attribute_name>]` instead of `.<attribute_name>` |
|
1567
|
-
| model | - | `:default` | `FormObj::ModelMapper` | The name of the model to which this attribute is mapped |
|
1568
|
-
| model_attribute | - | `<attribute_name>` | `FormObj::ModelMapper` | The name of the model attribute to which this form object attribute is mapped. Dot notation is used in order to map to nested model, ex. `"car.engine.power"`. Colon is used in front of the name if the model is hash, ex. `"car.:engine.power"` - means call to `#car` returns `Hash` so the model attribute should be accessed like `car[:engine].power`. `false` value means that attribute is not mapped. |
|
1569
|
-
| model_class | block* or `:class`** or dot notation for `:model_attribute`*** | `<attribute_name>.classify` | `FormObj::ModelMapper` | The class (or the name of the class) of the mapped model. |
|
1570
|
-
| model_nesting | block* or `:class`** | `true` | `FornObj::ModelMapper` | If attribute describes nested form object and has `model_nesting: false` the attributes of nested form will be called on the parent (upper level) model. If attribute describes array of form objects and has `model_nesting: false` the methods to access array elements (`:[]` etc.) will be called on the parent (upper level) model. |
|
1571
|
-
| primary_key | no block* and no `:class`** | `false` | `FormObj::Struct` | This attribute is the primary key of the form object. The mapped model attribute is considered to be a primary key for the corresponding model. |
|
1572
|
-
| primary_key | block* or `:class`** | - | `FormObj::Struct` | This attribute is either nested form object or array of form objects. The value of this parameter is the name of the primary key attribute of this form object. |
|
1573
|
-
\* block - means that there is block definition for the attribute
|
2080
|
+
##### 5.3.6. Parameter `read_from_model`
|
1574
2081
|
|
1575
|
-
|
2082
|
+
*Default value:* `true`
|
1576
2083
|
|
1577
|
-
|
2084
|
+
`false` value of this parameter prevents from reading attribute value from the model in
|
2085
|
+
`load_from_model(s)` methods.
|
2086
|
+
|
2087
|
+
```ruby
|
2088
|
+
class TeamForm < FormObj::Form
|
2089
|
+
include FormObj::ModelMapper
|
2090
|
+
|
2091
|
+
attribute :name
|
2092
|
+
attribute :year, read_from_model: false
|
2093
|
+
end
|
2094
|
+
|
2095
|
+
Team = Struct.new(:name, :year)
|
2096
|
+
|
2097
|
+
team = Team.new('Ferrari', 1950)
|
2098
|
+
team_form = TeamForm.new(name: 'McLaren', year: 1966)
|
2099
|
+
|
2100
|
+
team_form.load_from_model(team)
|
2101
|
+
team_form.name # => "Ferrari"
|
2102
|
+
team_form.year # => 1966
|
2103
|
+
```
|
2104
|
+
|
2105
|
+
##### 5.3.7. Parameter `write_to_model`
|
2106
|
+
|
2107
|
+
*Default value:* `true`
|
2108
|
+
|
2109
|
+
`false` value of this parameter
|
2110
|
+
- will prevent from writing attribute value to the model in `sync_to_model(s)` methods,
|
2111
|
+
- attribute will not be present in the hash generated by `to_model(s)_hash` methods,
|
2112
|
+
- attribute errors will not be copied from the model by `copy_errors_from_model(s)` methods.
|
2113
|
+
|
2114
|
+
```ruby
|
2115
|
+
class TeamForm < FormObj::Form
|
2116
|
+
include FormObj::ModelMapper
|
2117
|
+
|
2118
|
+
attribute :name
|
2119
|
+
attribute :year, write_to_model: false
|
2120
|
+
end
|
2121
|
+
|
2122
|
+
Team = Struct.new(:name, :year)
|
2123
|
+
|
2124
|
+
team = Team.new('Ferrari', 1950)
|
2125
|
+
team_form = TeamForm.new(name: 'McLaren', year: 1966)
|
2126
|
+
|
2127
|
+
team_form.sync_to_model(team)
|
2128
|
+
team.name # => "McLaren"
|
2129
|
+
team.year # => 1950
|
2130
|
+
|
2131
|
+
team_form.to_model_hash # => {:name=>"McLaren"}
|
2132
|
+
```
|
1578
2133
|
|
1579
2134
|
## Development
|
1580
2135
|
|
1581
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake
|
2136
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
1582
2137
|
|
1583
2138
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
1584
2139
|
|