virtus2 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +39 -0
- data/.rspec +2 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +18 -0
- data/Changelog.md +258 -0
- data/Gemfile +10 -0
- data/Guardfile +19 -0
- data/LICENSE +20 -0
- data/README.md +630 -0
- data/Rakefile +15 -0
- data/TODO.md +6 -0
- data/lib/virtus/attribute/accessor.rb +103 -0
- data/lib/virtus/attribute/boolean.rb +55 -0
- data/lib/virtus/attribute/builder.rb +182 -0
- data/lib/virtus/attribute/coercer.rb +45 -0
- data/lib/virtus/attribute/coercible.rb +20 -0
- data/lib/virtus/attribute/collection.rb +103 -0
- data/lib/virtus/attribute/default_value/from_callable.rb +35 -0
- data/lib/virtus/attribute/default_value/from_clonable.rb +35 -0
- data/lib/virtus/attribute/default_value/from_symbol.rb +35 -0
- data/lib/virtus/attribute/default_value.rb +51 -0
- data/lib/virtus/attribute/embedded_value.rb +67 -0
- data/lib/virtus/attribute/enum.rb +45 -0
- data/lib/virtus/attribute/hash.rb +130 -0
- data/lib/virtus/attribute/lazy_default.rb +18 -0
- data/lib/virtus/attribute/nullify_blank.rb +24 -0
- data/lib/virtus/attribute/strict.rb +26 -0
- data/lib/virtus/attribute.rb +245 -0
- data/lib/virtus/attribute_set.rb +240 -0
- data/lib/virtus/builder/hook_context.rb +51 -0
- data/lib/virtus/builder.rb +133 -0
- data/lib/virtus/class_inclusions.rb +48 -0
- data/lib/virtus/class_methods.rb +90 -0
- data/lib/virtus/coercer.rb +41 -0
- data/lib/virtus/configuration.rb +72 -0
- data/lib/virtus/const_missing_extensions.rb +18 -0
- data/lib/virtus/extensions.rb +105 -0
- data/lib/virtus/instance_methods.rb +218 -0
- data/lib/virtus/model.rb +68 -0
- data/lib/virtus/module_extensions.rb +88 -0
- data/lib/virtus/support/equalizer.rb +128 -0
- data/lib/virtus/support/options.rb +113 -0
- data/lib/virtus/support/type_lookup.rb +109 -0
- data/lib/virtus/value_object.rb +150 -0
- data/lib/virtus/version.rb +3 -0
- data/lib/virtus.rb +310 -0
- data/spec/integration/attributes_attribute_spec.rb +28 -0
- data/spec/integration/building_module_spec.rb +90 -0
- data/spec/integration/collection_member_coercion_spec.rb +96 -0
- data/spec/integration/custom_attributes_spec.rb +42 -0
- data/spec/integration/custom_collection_attributes_spec.rb +101 -0
- data/spec/integration/default_values_spec.rb +87 -0
- data/spec/integration/defining_attributes_spec.rb +86 -0
- data/spec/integration/embedded_value_spec.rb +50 -0
- data/spec/integration/extending_objects_spec.rb +35 -0
- data/spec/integration/hash_attributes_coercion_spec.rb +54 -0
- data/spec/integration/inheritance_spec.rb +42 -0
- data/spec/integration/injectible_coercers_spec.rb +48 -0
- data/spec/integration/mass_assignment_with_accessors_spec.rb +44 -0
- data/spec/integration/overriding_virtus_spec.rb +46 -0
- data/spec/integration/required_attributes_spec.rb +25 -0
- data/spec/integration/struct_as_embedded_value_spec.rb +28 -0
- data/spec/integration/using_modules_spec.rb +55 -0
- data/spec/integration/value_object_with_custom_constructor_spec.rb +42 -0
- data/spec/integration/virtus/instance_level_attributes_spec.rb +23 -0
- data/spec/integration/virtus/value_object_spec.rb +99 -0
- data/spec/shared/constants_helpers.rb +9 -0
- data/spec/shared/freeze_method_behavior.rb +40 -0
- data/spec/shared/idempotent_method_behaviour.rb +5 -0
- data/spec/shared/options_class_method.rb +19 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +43 -0
- data/spec/unit/virtus/attribute/boolean/value_coerced_predicate_spec.rb +25 -0
- data/spec/unit/virtus/attribute/class_methods/build_spec.rb +180 -0
- data/spec/unit/virtus/attribute/class_methods/coerce_spec.rb +32 -0
- data/spec/unit/virtus/attribute/coerce_spec.rb +129 -0
- data/spec/unit/virtus/attribute/coercible_predicate_spec.rb +20 -0
- data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +105 -0
- data/spec/unit/virtus/attribute/collection/coerce_spec.rb +74 -0
- data/spec/unit/virtus/attribute/collection/value_coerced_predicate_spec.rb +31 -0
- data/spec/unit/virtus/attribute/comparison_spec.rb +20 -0
- data/spec/unit/virtus/attribute/custom_collection_spec.rb +29 -0
- data/spec/unit/virtus/attribute/defined_spec.rb +20 -0
- data/spec/unit/virtus/attribute/embedded_value/class_methods/build_spec.rb +70 -0
- data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +91 -0
- data/spec/unit/virtus/attribute/get_spec.rb +32 -0
- data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +106 -0
- data/spec/unit/virtus/attribute/hash/coerce_spec.rb +92 -0
- data/spec/unit/virtus/attribute/lazy_predicate_spec.rb +20 -0
- data/spec/unit/virtus/attribute/rename_spec.rb +16 -0
- data/spec/unit/virtus/attribute/required_predicate_spec.rb +19 -0
- data/spec/unit/virtus/attribute/set_default_value_spec.rb +107 -0
- data/spec/unit/virtus/attribute/set_spec.rb +29 -0
- data/spec/unit/virtus/attribute/value_coerced_predicate_spec.rb +19 -0
- data/spec/unit/virtus/attribute_set/append_spec.rb +47 -0
- data/spec/unit/virtus/attribute_set/define_reader_method_spec.rb +36 -0
- data/spec/unit/virtus/attribute_set/define_writer_method_spec.rb +36 -0
- data/spec/unit/virtus/attribute_set/each_spec.rb +65 -0
- data/spec/unit/virtus/attribute_set/element_reference_spec.rb +17 -0
- data/spec/unit/virtus/attribute_set/element_set_spec.rb +64 -0
- data/spec/unit/virtus/attribute_set/merge_spec.rb +34 -0
- data/spec/unit/virtus/attribute_set/reset_spec.rb +71 -0
- data/spec/unit/virtus/attribute_spec.rb +229 -0
- data/spec/unit/virtus/attributes_reader_spec.rb +41 -0
- data/spec/unit/virtus/attributes_writer_spec.rb +51 -0
- data/spec/unit/virtus/class_methods/finalize_spec.rb +67 -0
- data/spec/unit/virtus/class_methods/new_spec.rb +39 -0
- data/spec/unit/virtus/config_spec.rb +13 -0
- data/spec/unit/virtus/element_reader_spec.rb +21 -0
- data/spec/unit/virtus/element_writer_spec.rb +19 -0
- data/spec/unit/virtus/freeze_spec.rb +41 -0
- data/spec/unit/virtus/model_spec.rb +197 -0
- data/spec/unit/virtus/module_spec.rb +174 -0
- data/spec/unit/virtus/set_default_attributes_spec.rb +32 -0
- data/spec/unit/virtus/value_object_spec.rb +138 -0
- data/virtus2.gemspec +26 -0
- metadata +225 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 931b9a71f0f9c09c2b861371f2ad57a7553f005b0ddb5920674dd8c2d2df12a6
|
4
|
+
data.tar.gz: 754af0ece66410668ceb1ea23c7a5aca49a9d7edf8c8fd4587e168c57369ff45
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bf30cb66aa3fb3c507d9cc7dac83749b3f30dfb51d701cd1797015e847573123042779bfa7ace1b666b28b8fe8f7f99503a533ed73f21e5a8006c6b0ed119ed1
|
7
|
+
data.tar.gz: 366a2a1989ffeaad24342f2c687835b4b48735cd7cd83916f2a8dfe6a8201c9397b931f03a2c64229c3dc00859607f5e421e597e15b93250ba737f3eedc0d021
|
data/.gitignore
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
*.swp
|
15
|
+
|
16
|
+
## Rubinius
|
17
|
+
*.rbc
|
18
|
+
.rbx
|
19
|
+
|
20
|
+
## PROJECT::GENERAL
|
21
|
+
*.gem
|
22
|
+
coverage
|
23
|
+
profiling
|
24
|
+
turbulence
|
25
|
+
rdoc
|
26
|
+
pkg
|
27
|
+
tmp
|
28
|
+
doc
|
29
|
+
log
|
30
|
+
.yardoc
|
31
|
+
measurements
|
32
|
+
|
33
|
+
## BUNDLER
|
34
|
+
.bundle
|
35
|
+
Gemfile.lock
|
36
|
+
bin/
|
37
|
+
|
38
|
+
## PROJECT::SPECIFIC
|
39
|
+
.idea
|
data/.rspec
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- README.md History.md LICENSE
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Current project status
|
2
|
+
|
3
|
+
Virtus recently hit it's 1.0 release (2013-10-16). The focus now is on bug-fixes and maintenance while [@solnic][solnic] is away from the project. An experimental branch will be kept up-to date where proposed features and API changes can be made. Please direct your questions and issues to [@elskwid][elskwid], the maintainer.
|
4
|
+
|
5
|
+
# Contributing to Virtus
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
10
|
+
* Commit, do not mess with Rakefile or version
|
11
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
12
|
+
* Send me a pull request. Bonus points for topic branches.
|
13
|
+
|
14
|
+
Author: [@solnic][solnic]
|
15
|
+
Maintainer: [@elskwid][elskwid]
|
16
|
+
|
17
|
+
[solnic]: https://github.com/solnic
|
18
|
+
[elskwid]: https://github.com/elskwid
|
data/Changelog.md
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
# v2.0.0 2021-06-07
|
2
|
+
|
3
|
+
* [changed] min ruby version is now 2.0 (solnic)
|
4
|
+
* [changed] inflecto was replaced with dry-inflector (solnic)
|
5
|
+
* [changed] equalizer was replaced with the internal virtus/equalizer (solnic)
|
6
|
+
* [changed] `Virtus::Attribute#==` was revised (see ef57af319334a1d4f3e0860acbde7c6d6f0eb8ef) (novikserg)
|
7
|
+
* [added] New method `Virtus::Atrribute::Collection#value_coerced?` (dslh)
|
8
|
+
* [fixed] Mass assignment bug fix (see #325) (novikserg)
|
9
|
+
|
10
|
+
[Compare v1.0.5..v2.0.0](https://github.com/solnic/virtus/compare/v1.0.5...v2.0.0)
|
11
|
+
|
12
|
+
# v1.0.5 2015-03-18
|
13
|
+
|
14
|
+
* [feature] Support for :nullify_blank option when configuring a virtus module (lucasmazza)
|
15
|
+
|
16
|
+
[Compare v1.0.4..v1.0.5](https://github.com/solnic/virtus/compare/v1.0.4...v1.0.5)
|
17
|
+
|
18
|
+
# v1.0.4 2015-01-03
|
19
|
+
|
20
|
+
* [feature] Support for :required option when configuring a virtus module (solnic)
|
21
|
+
|
22
|
+
[Compare v1.0.3..v1.0.4](https://github.com/solnic/virtus/compare/v1.0.3...v1.0.4)
|
23
|
+
|
24
|
+
# v1.0.3 2014-07-24
|
25
|
+
|
26
|
+
* [improvement] Expose attribute name in the exception when in strict mode (ntl)
|
27
|
+
* [improvement] Set #to_h as an alias to #to_hash (fnando)
|
28
|
+
* [fixed] Fix handling of nil in collection coercion (edgibbs)
|
29
|
+
* [fixed] Fix issues with using multiple virtus modules (trptcolin)
|
30
|
+
* [fixed] Fix handling of Range type (hampei)
|
31
|
+
* [fixed] Fix strict mode for collection and hash types (solnic)
|
32
|
+
|
33
|
+
[Compare v1.0.2..v1.0.3](https://github.com/solnic/virtus/compare/v1.0.2...v1.0.3)
|
34
|
+
|
35
|
+
# v1.0.2 2013-12-03
|
36
|
+
|
37
|
+
* [improvement] Don’t override already-defined default values when freezing (amarshall)
|
38
|
+
* [improvement] Improved performance of `AttributeSet#each` (Antti)
|
39
|
+
* updated axiom-types dependency to ~> 0.1 (solnic)
|
40
|
+
|
41
|
+
[Compare v1.0.1..v1.0.2](https://github.com/solnic/virtus/compare/v1.0.1...v1.0.2)
|
42
|
+
|
43
|
+
# v1.0.1 2013-12-10
|
44
|
+
|
45
|
+
* [feature] re-introduce `ValueObject#with`, which was removed in the past (senny)
|
46
|
+
* [fixed] strict mode for Boolean type (solnic)
|
47
|
+
|
48
|
+
[Compare v1.0.0..v1.0.1](https://github.com/solnic/virtus/compare/v1.0.0...v1.0.1)
|
49
|
+
|
50
|
+
# v1.0.0 2013-10-16
|
51
|
+
|
52
|
+
This release no longer works with Ruby 1.8.7.
|
53
|
+
|
54
|
+
* [BREAKING CHANGE] Integrated with axiom-types, most of the attribute sub-classes are gone (solnic)
|
55
|
+
* [feature] Configurable coercion via coercible integration (solnic)
|
56
|
+
* [feature] Strict mode for coercions via `:strict` option (solnic)
|
57
|
+
* [feature] Lazy-loaded default values via `:lazy` option (solnic)
|
58
|
+
* [feature] Finalizing models solving circular-dependency issue (see #81) (solnic)
|
59
|
+
* [feature] Ability to cherry-pick which extension should be included (solnic)
|
60
|
+
* [feature] Ability to inject a custom coercer object via `:coercer` option (solnic)
|
61
|
+
* [feature] Extension module builder with pre-defined configuration for attributes (elskwid & solnic)
|
62
|
+
* [feature] `Virtus::Attribute` exposes a public API - you can easily build, rename and clone attribute instances and use their coercion power (solnic)
|
63
|
+
* [feature] Ability to reset attributes to their default values (pewniak747)
|
64
|
+
* [changed] A meaningful error will be raised if a reserved name is used as an attribute name (solnic)
|
65
|
+
* [changed] Default value can be set via private and protected methods now (solnic)
|
66
|
+
* [changed] New syntax for value objects (solnic)
|
67
|
+
* [changed] Default values are now set in the constructor for non-lazy attributes (solnic)
|
68
|
+
* [deprecated] `Virtus::Attribute.coerce` in favor of `Virtus.coerce` or a customized configured module (solnic)
|
69
|
+
* [deprecated] `include Virtus` in favor of `include Virtus.model` (for classes) or `Virtus.module` (for modules) (solnic)
|
70
|
+
* [deprecated] `include Virtus::ValueObject` in favor of `include Virtus.value_object` (solnic)
|
71
|
+
* [deprecated] `Virtus#attributes` in favor of `Virtus#attribute_set` (solnic)
|
72
|
+
* [fixed] const missing hook now works correctly in modules too (cored)
|
73
|
+
* [fixed] value object with Hash type works correctly (solnic)
|
74
|
+
* [fixed] issues with value-object subclasses and `#==` method (solnic)
|
75
|
+
|
76
|
+
[Compare v0.5.4..v1.0.0](https://github.com/solnic/virtus/compare/v0.5.4...v1.0.0)
|
77
|
+
|
78
|
+
# v0.5.4 2012-12-20
|
79
|
+
|
80
|
+
* [feature] Allow *any* enumerable to be a collection attribute (aptinio)
|
81
|
+
* [feature] Add Integer.to_datetime and Float.to_datetime coercion (brutuscat)
|
82
|
+
* [fixed] Fixed a regression with Hash attribute introduced by key/member coercion (shingara)
|
83
|
+
* [fixed] Change leading non-significant digit type coercion to be coerced (maskact)
|
84
|
+
|
85
|
+
[Compare v0.5.3..v0.5.4](https://github.com/solnic/virtus/compare/v0.5.3...v0.5.4)
|
86
|
+
|
87
|
+
# v0.5.3 2012-12-13
|
88
|
+
|
89
|
+
* [feature] Added Hash member type coercion [example](https://github.com/solnic/virtus#hash-attributes-coercion) (greyblake)
|
90
|
+
* [fixed] Fixed issues with String=>Integer coercion and e-notation (greyblake)
|
91
|
+
* [changed] Replaced internal DescendantsTracker with the extracted gem (solnic)
|
92
|
+
* [internal] Switched to rspec 2 and mutant for mutation testing (mbj)
|
93
|
+
|
94
|
+
[Compare v0.5.2..v0.5.3](https://github.com/solnic/virtus/compare/v0.5.2...v0.5.3)
|
95
|
+
|
96
|
+
# v0.5.2 2012-09-01
|
97
|
+
|
98
|
+
* [feature] Object is now the default attribute type (dkubb)
|
99
|
+
* [fixed] Fix module inclusion problems (dkubb)
|
100
|
+
* [fixed] Evaluate default values when freezing an object (mbj)
|
101
|
+
* [fixed] String representation of a big integer is now properly coerced to an integer (greyblake)
|
102
|
+
* [changed] AttributeSet is now a module responsible for defining attribute methods (emmanuel)
|
103
|
+
|
104
|
+
[Compare v0.5.1..v0.5.2](https://github.com/solnic/virtus/compare/v0.5.1...v0.5.2)
|
105
|
+
|
106
|
+
# v0.5.1 2012-06-11
|
107
|
+
|
108
|
+
* [fixed] EV properly handle nil as the value (solnic)
|
109
|
+
|
110
|
+
[Compare v0.5.0..v0.5.1](https://github.com/solnic/virtus/compare/v0.5.0...v0.5.1)
|
111
|
+
|
112
|
+
# v0.5.0 2012-06-08
|
113
|
+
|
114
|
+
* [feature] Support for extending objects (solnic)
|
115
|
+
* [feature] Support for defining attributes in modules (solnic)
|
116
|
+
* [feature] Support for Struct as an EmbeddedValue or ValueObject attribute (solnic)
|
117
|
+
* [changed] Allow any input for EmbeddedValue and ValueObject constructors (solnic)
|
118
|
+
* [changed] ValueObject instances cannot be duped or cloned (senny)
|
119
|
+
|
120
|
+
[Compare v0.4.2..v0.5.0](https://github.com/solnic/virtus/compare/v0.4.2...v0.5.0)
|
121
|
+
|
122
|
+
# v0.4.2 2012-05-08
|
123
|
+
|
124
|
+
* [updated] Bump backports dep to ~> 2.5.3 (solnic)
|
125
|
+
|
126
|
+
[Compare v0.4.1..v0.4.2](https://github.com/solnic/virtus/compare/v0.4.1...v0.4.2)
|
127
|
+
|
128
|
+
# v0.4.1 2012-05-06
|
129
|
+
|
130
|
+
* [changed] backports gem is now a runtime dependency (solnic)
|
131
|
+
* [BREAKING CHANGE] Renamed Virtus::DefaultValue#evaluate => Virtus::DefaultValue#call (solnic)
|
132
|
+
* [BREAKING CHANGE] Renamed Virtus::ValueObject::Equalizer to Virtus::Equalizer (dkubb)
|
133
|
+
|
134
|
+
[Compare v0.4.0..v0.4.1](https://github.com/solnic/virtus/compare/v0.4.0...v0.4.1)
|
135
|
+
|
136
|
+
# v0.4.0 2012-03-22
|
137
|
+
|
138
|
+
* [improvement] Add a caching mechanism for type lookups (solnic)
|
139
|
+
* [fixed] Fix attributes mass-assignment when nil is passed (fgrehm)
|
140
|
+
* [changed] Replace usage of #to_hash with Hash.try_convert (dkubb)
|
141
|
+
|
142
|
+
[Compare v0.3.0..v0.4.0](https://github.com/solnic/virtus/compare/v0.3.0...v0.4.0)
|
143
|
+
|
144
|
+
# v0.3.0 2012-02-25
|
145
|
+
|
146
|
+
* [feature] Support for default values from a symbol (which can be a method name) (solnic)
|
147
|
+
* [feature] Support for mass-assignment via custom setters not generated with attribute (fgrehm)
|
148
|
+
* [feature] Virtus::Coercion::String.to_constant handles namespaced names (dkubb)
|
149
|
+
* [feature] New coercion: Virtus::Coercion::Object.to_array (dkubb)
|
150
|
+
* [feature] New coercion: Virtus::Coercion::Object.to_hash (dkubb)
|
151
|
+
* [feature] New coercion: Virtus::Coercion::Object.to_string (dkubb)
|
152
|
+
* [feature] New coercion: Virtus::Coercion::Object.to_integer (dkubb)
|
153
|
+
* [changed] EmbeddedValue relies on @primitive setting rather than @model (mbj)
|
154
|
+
* [BREAKING CHANGE] Removed Attribute#writer_visibility in favor of Attribute#public_writer? (solnic)
|
155
|
+
* [BREAKING CHANGE] Removed Attribute#reader_visibility in favor of Attribute#public_reader? (solnic)
|
156
|
+
* [BREAKING CHANGE] Removed Attribute#instance_variable_name - this is a private ivar (solnic)
|
157
|
+
* [BREAKING CHANGE] Removed Equalizer#host_name and Equalizer#keys (solnic)
|
158
|
+
|
159
|
+
[Compare v0.2.0..v0.3.0](https://github.com/solnic/virtus/compare/v0.2.0...v0.3.0)
|
160
|
+
|
161
|
+
# v0.2.0 2012-02-08
|
162
|
+
|
163
|
+
* [feature] Support for Value Objects (emmanuel)
|
164
|
+
* [feature] New Symbol attribute (solnic)
|
165
|
+
* [feature] Time => Integer coercion (solnic)
|
166
|
+
|
167
|
+
[Compare v0.1.0..v0.2.0](https://github.com/solnic/virtus/compare/v0.1.0...v0.2.0)
|
168
|
+
|
169
|
+
# v0.1.0 2012-02-05
|
170
|
+
|
171
|
+
* [feature] New EmbeddedValue attribute (solnic)
|
172
|
+
* [feature] Array and Set attributes support member coercions (emmanuel)
|
173
|
+
* [feature] Support for scientific notation handling in string => integer coercion (dkubb)
|
174
|
+
* [feature] Handling of string => numeric coercion with a leading + sign (dkubb)
|
175
|
+
* [changed] Update Boolean coercion to handle "on", "off", "y", "n", "yes", "no" (dkubb)
|
176
|
+
|
177
|
+
[Compare v0.0.10..v0.1.0](https://github.com/solnic/virtus/compare/v0.0.10...v0.1.0)
|
178
|
+
|
179
|
+
# v0.0.10 2011-11-21
|
180
|
+
|
181
|
+
* [fixed] Default values are now duped on evaluate (rclosner)
|
182
|
+
* [fixed] Allow to override attribute mutator methods (senny)
|
183
|
+
|
184
|
+
[Compare v0.0.9..v0.0.10](https://github.com/solnic/virtus/compare/v0.0.9...v0.0.10)
|
185
|
+
|
186
|
+
# v0.0.9 2011-10-11
|
187
|
+
|
188
|
+
* [fixed] Fix in type lookup for anonymous classes (dkubb)
|
189
|
+
|
190
|
+
[Compare v0.0.8..v0.0.9](https://github.com/solnic/virtus/compare/v0.0.8...v0.0.9)
|
191
|
+
|
192
|
+
# v0.0.8 2011-08-25
|
193
|
+
|
194
|
+
* [fixed] Fixed conflict with ActiveModel (RichGuk)
|
195
|
+
* [changed] Renamed Coercion::String.to_class => Coercion::String.to_constant (emmanuel)
|
196
|
+
|
197
|
+
[Compare v0.0.7..v0.0.8](https://github.com/solnic/virtus/compare/v0.0.7...v0.0.8)
|
198
|
+
|
199
|
+
# v0.0.7 2011-07-31
|
200
|
+
|
201
|
+
* [BREAKING CHANGE] Attribute.primitive? has been removed (solnic)
|
202
|
+
* [fixed] Added missing coercion_method setting to Virtus::Attribute::Object (solnic)
|
203
|
+
* [general] Default value logic has been extracted into Attribute::DefaultValue class (solnic)
|
204
|
+
* [added] Virtus::Attribute::Class (solnic)
|
205
|
+
|
206
|
+
[Compare v0.0.6..v0.0.7](https://github.com/solnic/virtus/compare/v0.0.6...v0.0.7)
|
207
|
+
|
208
|
+
# v0.0.6 2011-07-30
|
209
|
+
|
210
|
+
* [BREAKING CHANGE] Moved Virtus.determine_type to a shared module Virtus::TypeLookup (dkubb)
|
211
|
+
* [BREAKING CHANGE] Attribute#typecast_to_primitive has been replaced by Attribute#coerce (solnic)
|
212
|
+
* [BREAKING CHANGE] Attribute#typecast logic was moved to Attribute#set which is now a public method (solnic)
|
213
|
+
* [feature] Added support for default values (solnic)
|
214
|
+
* [general] Added custom inspect for Attribute classes (solnic)
|
215
|
+
* [general] Added backports as a development dependency (dkubb)
|
216
|
+
* [changed] Options API has been extracted from Attribute to a support module Virtus::Options (solnic)
|
217
|
+
* [changed] Typecast classes have been replaced by a new hierarchy of Coercion classes like Coercion::String, Coercion::Integer etc. (solnic)
|
218
|
+
* [changed] Attribute#get, #get!, #set, #set! & #coerce are now part of the public API (solnic)
|
219
|
+
|
220
|
+
[Compare v0.0.5..v0.0.6](https://github.com/solnic/virtus/compare/v0.0.5...v0.0.6)
|
221
|
+
|
222
|
+
# v0.0.5 2011-07-10
|
223
|
+
|
224
|
+
* [bugfix] Fixed DescendantsTracker + ActiveSupport collision (dkubb)
|
225
|
+
|
226
|
+
[Compare v0.0.4..v0.0.5](https://github.com/solnic/virtus/compare/v0.0.4...v0.0.5)
|
227
|
+
|
228
|
+
# v0.0.4 2011-07-08
|
229
|
+
|
230
|
+
* [BREAKING CHANGE] attributes hash has been replaced by a specialized class AttributeSet (dkubb)
|
231
|
+
* [BREAKING CHANGE] Virtus::ClassMethods.attribute returns self instead of a created attribute (solnic)
|
232
|
+
* [changed] descendants tracking has been extracted into DescendantsTracker module (dkubb)
|
233
|
+
* [changed] Instance #primitive? method has been replaced by class utility method Virtus::Attribute.primitive? (solnic)
|
234
|
+
* [changed] Virtus::Attribute::String#typecast_to_primitive delegates to Virtus::Typecast::String.call (solnic)
|
235
|
+
|
236
|
+
[Compare v0.0.3..v0.0.4](https://github.com/solnic/virtus/compare/v0.0.3...v0.0.4)
|
237
|
+
|
238
|
+
# v0.0.3 2011-06-09
|
239
|
+
|
240
|
+
* [BREAKING CHANGE] Attribute classes were moved to Virtus::Attribute namespace (solnic)
|
241
|
+
* [BREAKING CHANGE] Attribute instance no longer holds the reference to a model (solnic)
|
242
|
+
* [BREAKING CHANGE] #typecast no longer receives an instance of a model (override #set which calls #typecast if you need that) (solnic)
|
243
|
+
* [changed] Adding reader/writer methods was moved from the attribute constructor to Virtus::ClassMethods.attribute (solnic)
|
244
|
+
* [changed] Typecast logic has been moved into separate classes (see Virtus::Typecast) (solnic)
|
245
|
+
* [added] Virtus::Attribute::DateTime#typecast supports objects which implement #to_datetime (solnic)
|
246
|
+
* [general] Internals have been cleaned up, simplified and properly documented (solnic)
|
247
|
+
|
248
|
+
[Compare v0.0.2..v0.0.3](https://github.com/solnic/virtus/compare/v0.0.2...v0.0.3)
|
249
|
+
|
250
|
+
# v0.0.2 2011-06-06
|
251
|
+
|
252
|
+
* [bugfix] Fixed #typecast in custom attribute classes (solnic)
|
253
|
+
|
254
|
+
[Compare v0.0.1..v0.0.2](https://github.com/solnic/virtus/compare/v0.0.1...v0.0.2)
|
255
|
+
|
256
|
+
# v0.0.1 2011-06-04
|
257
|
+
|
258
|
+
First public release :)
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
guard :rspec, spec_paths: 'spec/unit' do
|
2
|
+
#run all specs if configuration is modified
|
3
|
+
watch('Guardfile') { 'spec' }
|
4
|
+
watch('Gemfile.lock') { 'spec' }
|
5
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
6
|
+
|
7
|
+
# run all specs if supporting files files are modified
|
8
|
+
watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec' }
|
9
|
+
|
10
|
+
# run unit specs if associated lib code is modified
|
11
|
+
watch(%r{\Alib/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}"] }
|
12
|
+
watch(%r{\Alib/(.+)/support/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}/#{m[2]}"] }
|
13
|
+
watch("lib/#{File.basename(File.expand_path('../', __FILE__))}.rb") { 'spec' }
|
14
|
+
|
15
|
+
# run a spec if it is modified
|
16
|
+
watch(%r{\Aspec/(?:unit|integration)/.+_spec\.rb\z})
|
17
|
+
|
18
|
+
notification :tmux, :display_message => true
|
19
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011-2013 Piotr Solnica
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,630 @@
|
|
1
|
+
[gem]: https://rubygems.org/gems/virtus2
|
2
|
+
|
3
|
+
NOTES
|
4
|
+
------------
|
5
|
+
|
6
|
+
Although the original author of this project has moved on to the `dry-` family of gems, which provides much more power and flexibility, we believe that there is still value in the simplicity and elegance of this original library in cases where that power is unnecessary. Hence the republishing of this gem as `virtus2` to continue to improve and update the gem.
|
7
|
+
|
8
|
+
The `dry-` gems have a lot of community support and we would recommend looking there before coming back to this gem only if you think the complexity they provide is more than you need.
|
9
|
+
|
10
|
+
Virtus
|
11
|
+
======
|
12
|
+
|
13
|
+
[![Gem Version](https://badge.fury.io/rb/virtus2.svg)][gem]
|
14
|
+
|
15
|
+
Virtus allows you to define attributes on classes, modules or class instances with
|
16
|
+
optional information about types, reader/writer method visibility and coercion
|
17
|
+
behavior. It supports a lot of coercions and advanced mapping of embedded objects
|
18
|
+
and collections.
|
19
|
+
|
20
|
+
You can use it in many different contexts like:
|
21
|
+
|
22
|
+
* Input parameter sanitization and coercion in web applications
|
23
|
+
* Mapping JSON to domain objects
|
24
|
+
* Encapsulating data-access in Value Objects
|
25
|
+
* Domain model prototyping
|
26
|
+
|
27
|
+
And probably more.
|
28
|
+
|
29
|
+
Installation
|
30
|
+
------------
|
31
|
+
|
32
|
+
``` terminal
|
33
|
+
$ gem install virtus
|
34
|
+
```
|
35
|
+
|
36
|
+
or in your **Gemfile**
|
37
|
+
|
38
|
+
``` ruby
|
39
|
+
gem 'virtus'
|
40
|
+
```
|
41
|
+
|
42
|
+
Examples
|
43
|
+
--------
|
44
|
+
|
45
|
+
### Using Virtus with Classes
|
46
|
+
|
47
|
+
You can create classes extended with Virtus and define attributes:
|
48
|
+
|
49
|
+
``` ruby
|
50
|
+
class User
|
51
|
+
include Virtus.model
|
52
|
+
|
53
|
+
attribute :name, String
|
54
|
+
attribute :age, Integer
|
55
|
+
attribute :birthday, DateTime
|
56
|
+
end
|
57
|
+
|
58
|
+
user = User.new(:name => 'Piotr', :age => 31)
|
59
|
+
user.attributes # => { :name => "Piotr", :age => 31, :birthday => nil }
|
60
|
+
|
61
|
+
user.name # => "Piotr"
|
62
|
+
|
63
|
+
user.age = '31' # => 31
|
64
|
+
user.age.class # => Fixnum
|
65
|
+
|
66
|
+
user.birthday = 'November 18th, 1983' # => #<DateTime: 1983-11-18T00:00:00+00:00 (4891313/2,0/1,2299161)>
|
67
|
+
|
68
|
+
# mass-assignment
|
69
|
+
user.attributes = { :name => 'Jane', :age => 21 }
|
70
|
+
user.name # => "Jane"
|
71
|
+
user.age # => 21
|
72
|
+
```
|
73
|
+
|
74
|
+
### Cherry-picking extensions
|
75
|
+
|
76
|
+
``` ruby
|
77
|
+
# include attribute DSL + constructor + mass-assignment
|
78
|
+
class User
|
79
|
+
include Virtus.model
|
80
|
+
|
81
|
+
attribute :name, String
|
82
|
+
end
|
83
|
+
|
84
|
+
user = User.new(:name => 'Piotr')
|
85
|
+
user.attributes = { :name => 'John' }
|
86
|
+
user.attributes
|
87
|
+
# => {:name => 'John'}
|
88
|
+
|
89
|
+
# include attribute DSL + constructor
|
90
|
+
class User
|
91
|
+
include Virtus.model(:mass_assignment => false)
|
92
|
+
|
93
|
+
attribute :name, String
|
94
|
+
end
|
95
|
+
|
96
|
+
User.new(:name => 'Piotr')
|
97
|
+
|
98
|
+
# include just the attribute DSL
|
99
|
+
class User
|
100
|
+
include Virtus.model(:constructor => false, :mass_assignment => false)
|
101
|
+
|
102
|
+
attribute :name, String
|
103
|
+
end
|
104
|
+
|
105
|
+
user = User.new
|
106
|
+
user.name = 'Piotr'
|
107
|
+
```
|
108
|
+
|
109
|
+
### Using Virtus with Modules
|
110
|
+
|
111
|
+
You can create modules extended with Virtus and define attributes for later
|
112
|
+
inclusion in your classes:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
module Name
|
116
|
+
include Virtus.module
|
117
|
+
|
118
|
+
attribute :name, String
|
119
|
+
end
|
120
|
+
|
121
|
+
module Age
|
122
|
+
include Virtus.module(:coerce => false)
|
123
|
+
|
124
|
+
attribute :age, Integer
|
125
|
+
end
|
126
|
+
|
127
|
+
class User
|
128
|
+
include Name, Age
|
129
|
+
end
|
130
|
+
|
131
|
+
user = User.new(:name => 'John', :age => 30)
|
132
|
+
```
|
133
|
+
|
134
|
+
### Dynamically Extending Instances
|
135
|
+
|
136
|
+
It's also possible to dynamically extend an object with Virtus:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
class User
|
140
|
+
# nothing here
|
141
|
+
end
|
142
|
+
|
143
|
+
user = User.new
|
144
|
+
user.extend(Virtus.model)
|
145
|
+
user.attribute :name, String
|
146
|
+
user.name = 'John'
|
147
|
+
user.name # => 'John'
|
148
|
+
```
|
149
|
+
|
150
|
+
### Default Values
|
151
|
+
|
152
|
+
``` ruby
|
153
|
+
class Page
|
154
|
+
include Virtus.model
|
155
|
+
|
156
|
+
attribute :title, String
|
157
|
+
|
158
|
+
# default from a singleton value (integer in this case)
|
159
|
+
attribute :views, Integer, :default => 0
|
160
|
+
|
161
|
+
# default from a singleton value (boolean in this case)
|
162
|
+
attribute :published, Boolean, :default => false
|
163
|
+
|
164
|
+
# default from a callable object (proc in this case)
|
165
|
+
attribute :slug, String, :default => lambda { |page, attribute| page.title.downcase.gsub(' ', '-') }
|
166
|
+
|
167
|
+
# default from a method name as symbol
|
168
|
+
attribute :editor_title, String, :default => :default_editor_title
|
169
|
+
|
170
|
+
def default_editor_title
|
171
|
+
published? ? title : "UNPUBLISHED: #{title}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
page = Page.new(:title => 'Virtus README')
|
176
|
+
page.slug # => 'virtus-readme'
|
177
|
+
page.views # => 0
|
178
|
+
page.published # => false
|
179
|
+
page.editor_title # => "UNPUBLISHED: Virtus README"
|
180
|
+
|
181
|
+
page.views = 10
|
182
|
+
page.views # => 10
|
183
|
+
page.reset_attribute(:views) # => 0
|
184
|
+
page.views # => 0
|
185
|
+
```
|
186
|
+
|
187
|
+
### Default values on dynamically extended instances
|
188
|
+
|
189
|
+
This requires you to set `:lazy` option because default values are set in the
|
190
|
+
constructor if it's set to false (which is the default setting):
|
191
|
+
|
192
|
+
``` ruby
|
193
|
+
User = Class.new
|
194
|
+
user = User.new
|
195
|
+
user.extend(Virtus.model)
|
196
|
+
user.attribute :name, String, default: 'jane', lazy: true
|
197
|
+
user.name # => "jane"
|
198
|
+
```
|
199
|
+
|
200
|
+
### Embedded Value
|
201
|
+
|
202
|
+
``` ruby
|
203
|
+
class City
|
204
|
+
include Virtus.model
|
205
|
+
|
206
|
+
attribute :name, String
|
207
|
+
end
|
208
|
+
|
209
|
+
class Address
|
210
|
+
include Virtus.model
|
211
|
+
|
212
|
+
attribute :street, String
|
213
|
+
attribute :zipcode, String
|
214
|
+
attribute :city, City
|
215
|
+
end
|
216
|
+
|
217
|
+
class User
|
218
|
+
include Virtus.model
|
219
|
+
|
220
|
+
attribute :name, String
|
221
|
+
attribute :address, Address
|
222
|
+
end
|
223
|
+
|
224
|
+
user = User.new(:address => {
|
225
|
+
:street => 'Street 1/2', :zipcode => '12345', :city => { :name => 'NYC' } })
|
226
|
+
|
227
|
+
user.address.street # => "Street 1/2"
|
228
|
+
user.address.city.name # => "NYC"
|
229
|
+
```
|
230
|
+
|
231
|
+
### Collection Member Coercions
|
232
|
+
|
233
|
+
``` ruby
|
234
|
+
# Support "primitive" classes
|
235
|
+
class Book
|
236
|
+
include Virtus.model
|
237
|
+
|
238
|
+
attribute :page_numbers, Array[Integer]
|
239
|
+
end
|
240
|
+
|
241
|
+
book = Book.new(:page_numbers => %w[1 2 3])
|
242
|
+
book.page_numbers # => [1, 2, 3]
|
243
|
+
|
244
|
+
# Support EmbeddedValues, too!
|
245
|
+
class Address
|
246
|
+
include Virtus.model
|
247
|
+
|
248
|
+
attribute :address, String
|
249
|
+
attribute :locality, String
|
250
|
+
attribute :region, String
|
251
|
+
attribute :postal_code, String
|
252
|
+
end
|
253
|
+
|
254
|
+
class PhoneNumber
|
255
|
+
include Virtus.model
|
256
|
+
|
257
|
+
attribute :number, String
|
258
|
+
end
|
259
|
+
|
260
|
+
class User
|
261
|
+
include Virtus.model
|
262
|
+
|
263
|
+
attribute :phone_numbers, Array[PhoneNumber]
|
264
|
+
attribute :addresses, Set[Address]
|
265
|
+
end
|
266
|
+
|
267
|
+
user = User.new(
|
268
|
+
:phone_numbers => [
|
269
|
+
{ :number => '212-555-1212' },
|
270
|
+
{ :number => '919-444-3265' } ],
|
271
|
+
:addresses => [
|
272
|
+
{ :address => '1234 Any St.', :locality => 'Anytown', :region => "DC", :postal_code => "21234" } ])
|
273
|
+
|
274
|
+
user.phone_numbers # => [#<PhoneNumber:0x007fdb2d3bef88 @number="212-555-1212">, #<PhoneNumber:0x007fdb2d3beb00 @number="919-444-3265">]
|
275
|
+
|
276
|
+
user.addresses # => #<Set: {#<Address:0x007fdb2d3be448 @address="1234 Any St.", @locality="Anytown", @region="DC", @postal_code="21234">}>
|
277
|
+
```
|
278
|
+
|
279
|
+
### Hash attributes coercion
|
280
|
+
|
281
|
+
``` ruby
|
282
|
+
class Package
|
283
|
+
include Virtus.model
|
284
|
+
|
285
|
+
attribute :dimensions, Hash[Symbol => Float]
|
286
|
+
end
|
287
|
+
|
288
|
+
package = Package.new(:dimensions => { 'width' => "2.2", :height => 2, "length" => 4.5 })
|
289
|
+
package.dimensions # => { :width => 2.2, :height => 2.0, :length => 4.5 }
|
290
|
+
```
|
291
|
+
|
292
|
+
### IMPORTANT note about Boolean type
|
293
|
+
|
294
|
+
Be aware that some libraries may do a terrible thing and define a global Boolean
|
295
|
+
constant which breaks virtus' constant type lookup, if you see issues with the
|
296
|
+
boolean type you can workaround it like that:
|
297
|
+
|
298
|
+
``` ruby
|
299
|
+
class User
|
300
|
+
include Virtus.model
|
301
|
+
|
302
|
+
attribute :admin, Axiom::Types::Boolean
|
303
|
+
end
|
304
|
+
```
|
305
|
+
|
306
|
+
This will be improved in Virtus 2.0.
|
307
|
+
|
308
|
+
### IMPORTANT note about member coercions
|
309
|
+
|
310
|
+
Virtus performs coercions only when a value is being assigned. If you mutate the value later on using its own
|
311
|
+
interfaces then coercion won't be triggered.
|
312
|
+
|
313
|
+
Here's an example:
|
314
|
+
|
315
|
+
``` ruby
|
316
|
+
class Book
|
317
|
+
include Virtus.model
|
318
|
+
|
319
|
+
attribute :title, String
|
320
|
+
end
|
321
|
+
|
322
|
+
class Library
|
323
|
+
include Virtus.model
|
324
|
+
|
325
|
+
attribute :books, Array[Book]
|
326
|
+
end
|
327
|
+
|
328
|
+
library = Library.new
|
329
|
+
|
330
|
+
# This will coerce Hash to a Book instance
|
331
|
+
library.books = [ { :title => 'Introduction to Virtus' } ]
|
332
|
+
|
333
|
+
# This WILL NOT COERCE the value because you mutate the books array with Array#<<
|
334
|
+
library.books << { :title => 'Another Introduction to Virtus' }
|
335
|
+
```
|
336
|
+
|
337
|
+
A suggested solution to this problem would be to introduce your own class instead of using Array and implement
|
338
|
+
mutation methods that perform coercions. For example:
|
339
|
+
|
340
|
+
``` ruby
|
341
|
+
class Book
|
342
|
+
include Virtus.model
|
343
|
+
|
344
|
+
attribute :title, String
|
345
|
+
end
|
346
|
+
|
347
|
+
class BookCollection < Array
|
348
|
+
def <<(book)
|
349
|
+
if book.kind_of?(Hash)
|
350
|
+
super(Book.new(book))
|
351
|
+
else
|
352
|
+
super
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
class Library
|
358
|
+
include Virtus.model
|
359
|
+
|
360
|
+
attribute :books, BookCollection[Book]
|
361
|
+
end
|
362
|
+
|
363
|
+
library = Library.new
|
364
|
+
library.books << { :title => 'Another Introduction to Virtus' }
|
365
|
+
```
|
366
|
+
|
367
|
+
### Value Objects
|
368
|
+
|
369
|
+
``` ruby
|
370
|
+
class GeoLocation
|
371
|
+
include Virtus.value_object
|
372
|
+
|
373
|
+
values do
|
374
|
+
attribute :latitude, Float
|
375
|
+
attribute :longitude, Float
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
class Venue
|
380
|
+
include Virtus.value_object
|
381
|
+
|
382
|
+
values do
|
383
|
+
attribute :name, String
|
384
|
+
attribute :location, GeoLocation
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
venue = Venue.new(
|
389
|
+
:name => 'Pub',
|
390
|
+
:location => { :latitude => 37.160317, :longitude => -98.437500 })
|
391
|
+
|
392
|
+
venue.location.latitude # => 37.160317
|
393
|
+
venue.location.longitude # => -98.4375
|
394
|
+
|
395
|
+
# Supports object's equality
|
396
|
+
|
397
|
+
venue_other = Venue.new(
|
398
|
+
:name => 'Other Pub',
|
399
|
+
:location => { :latitude => 37.160317, :longitude => -98.437500 })
|
400
|
+
|
401
|
+
venue.location === venue_other.location # => true
|
402
|
+
```
|
403
|
+
|
404
|
+
### Custom Coercions
|
405
|
+
|
406
|
+
``` ruby
|
407
|
+
require 'json'
|
408
|
+
|
409
|
+
class Json < Virtus::Attribute
|
410
|
+
def coerce(value)
|
411
|
+
value.is_a?(::Hash) ? value : JSON.parse(value)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
class User
|
416
|
+
include Virtus.model
|
417
|
+
|
418
|
+
attribute :info, Json, default: {}
|
419
|
+
end
|
420
|
+
|
421
|
+
user = User.new
|
422
|
+
user.info = '{"email":"john@domain.com"}' # => {"email"=>"john@domain.com"}
|
423
|
+
user.info.class # => Hash
|
424
|
+
|
425
|
+
# With a custom attribute encapsulating coercion-specific configuration
|
426
|
+
class NoisyString < Virtus::Attribute
|
427
|
+
def coerce(value)
|
428
|
+
value.to_s.upcase
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
class User
|
433
|
+
include Virtus.model
|
434
|
+
|
435
|
+
attribute :scream, NoisyString
|
436
|
+
end
|
437
|
+
|
438
|
+
user = User.new(:scream => 'hello world!')
|
439
|
+
user.scream # => "HELLO WORLD!"
|
440
|
+
```
|
441
|
+
|
442
|
+
### Private Attributes
|
443
|
+
|
444
|
+
``` ruby
|
445
|
+
class User
|
446
|
+
include Virtus.model
|
447
|
+
|
448
|
+
attribute :unique_id, String, :writer => :private
|
449
|
+
|
450
|
+
def set_unique_id(id)
|
451
|
+
self.unique_id = id
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
user = User.new(:unique_id => '1234-1234')
|
456
|
+
user.unique_id # => nil
|
457
|
+
|
458
|
+
user.unique_id = '1234-1234' # => NoMethodError: private method `unique_id='
|
459
|
+
|
460
|
+
user.set_unique_id('1234-1234')
|
461
|
+
user.unique_id # => '1234-1234'
|
462
|
+
```
|
463
|
+
|
464
|
+
### Overriding setters
|
465
|
+
|
466
|
+
``` ruby
|
467
|
+
class User
|
468
|
+
include Virtus.model
|
469
|
+
|
470
|
+
attribute :name, String
|
471
|
+
|
472
|
+
def name=(new_name)
|
473
|
+
custom_name = nil
|
474
|
+
if new_name == "Godzilla"
|
475
|
+
custom_name = "Can't tell"
|
476
|
+
end
|
477
|
+
super custom_name || new_name
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
user = User.new(name: "Frank")
|
482
|
+
user.name # => 'Frank'
|
483
|
+
|
484
|
+
user = User.new(name: "Godzilla")
|
485
|
+
user.name # => 'Can't tell'
|
486
|
+
|
487
|
+
```
|
488
|
+
|
489
|
+
## Strict Coercion Mode
|
490
|
+
|
491
|
+
By default Virtus returns the input value even when it couldn't coerce it to the expected type.
|
492
|
+
If you want to catch such cases in a noisy way you can use the strict mode in which
|
493
|
+
Virtus raises an exception when it failed to coerce an input value.
|
494
|
+
|
495
|
+
``` ruby
|
496
|
+
class User
|
497
|
+
include Virtus.model(:strict => true)
|
498
|
+
|
499
|
+
attribute :admin, Boolean
|
500
|
+
end
|
501
|
+
|
502
|
+
# this will raise an error
|
503
|
+
User.new :admin => "can't really say if true or false"
|
504
|
+
```
|
505
|
+
|
506
|
+
## Nullify Blank Strings Mode
|
507
|
+
|
508
|
+
If you want to replace empty Strings with `nil` values (since they can't be
|
509
|
+
coerced into the expected type), you can use the `:nullify_blank` option.
|
510
|
+
|
511
|
+
``` ruby
|
512
|
+
class User
|
513
|
+
include Virtus.model(:nullify_blank => true)
|
514
|
+
|
515
|
+
attribute :birthday, Date
|
516
|
+
end
|
517
|
+
|
518
|
+
User.new(:birthday => "").birthday # => nil
|
519
|
+
```
|
520
|
+
|
521
|
+
### Enums
|
522
|
+
|
523
|
+
Virtus supports enums out of the box, represented by a list of Symbols:
|
524
|
+
|
525
|
+
```ruby
|
526
|
+
class FileProcessor
|
527
|
+
include Virtus.model(strict: true)
|
528
|
+
|
529
|
+
attribute :mode, Enum[:read, :write]
|
530
|
+
end
|
531
|
+
processor = FileProcessor.new(mode: :execute) # CoercionError!
|
532
|
+
processor = FileProcessor.new(mode: :read).mode # :read
|
533
|
+
```
|
534
|
+
|
535
|
+
## Building modules with custom configuration
|
536
|
+
|
537
|
+
You can also build Virtus modules that contain their own configuration.
|
538
|
+
|
539
|
+
```ruby
|
540
|
+
YupNopeBooleans = Virtus.model { |mod|
|
541
|
+
mod.coerce = true
|
542
|
+
mod.coercer.config.string.boolean_map = { 'nope' => false, 'yup' => true }
|
543
|
+
}
|
544
|
+
|
545
|
+
class User
|
546
|
+
include YupNopeBooleans
|
547
|
+
|
548
|
+
attribute :name, String
|
549
|
+
attribute :admin, Boolean
|
550
|
+
end
|
551
|
+
|
552
|
+
# Or just include the module straight away ...
|
553
|
+
class User
|
554
|
+
include Virtus.model(:coerce => false)
|
555
|
+
|
556
|
+
attribute :name, String
|
557
|
+
attribute :admin, Boolean
|
558
|
+
end
|
559
|
+
```
|
560
|
+
|
561
|
+
## Attribute Finalization and Circular Dependencies
|
562
|
+
|
563
|
+
If a type references another type which happens to not be available yet you need
|
564
|
+
to use lazy-finalization of attributes and finalize virtus manually after all
|
565
|
+
types have been already loaded:
|
566
|
+
|
567
|
+
``` ruby
|
568
|
+
# in blog.rb
|
569
|
+
class Blog
|
570
|
+
include Virtus.model(:finalize => false)
|
571
|
+
|
572
|
+
attribute :posts, Array['Post']
|
573
|
+
end
|
574
|
+
|
575
|
+
# in post.rb
|
576
|
+
class Post
|
577
|
+
include Virtus.model(:finalize => false)
|
578
|
+
|
579
|
+
attribute :blog, 'Blog'
|
580
|
+
end
|
581
|
+
|
582
|
+
# after loading both files just do:
|
583
|
+
Virtus.finalize
|
584
|
+
|
585
|
+
# constants will be resolved:
|
586
|
+
Blog.attribute_set[:posts].member_type.primitive # => Post
|
587
|
+
Post.attribute_set[:blog].type.primitive # => Blog
|
588
|
+
```
|
589
|
+
|
590
|
+
## Plugins / Extensions
|
591
|
+
|
592
|
+
List of plugins/extensions that add features to Virtus:
|
593
|
+
|
594
|
+
* [virtus-localized](https://github.com/XescuGC/virtus-localized): Localize the attributes
|
595
|
+
* [virtus-relations](https://github.com/smanolloff/virtus-relations): Add relations to Virtus objects
|
596
|
+
|
597
|
+
Ruby version support
|
598
|
+
--------------------
|
599
|
+
|
600
|
+
Virtus is known to work correctly with the following rubies:
|
601
|
+
|
602
|
+
* 1.9.3
|
603
|
+
* 2.0.0
|
604
|
+
* 2.1.2
|
605
|
+
* 2.7.0
|
606
|
+
* 3.0.0
|
607
|
+
* jruby
|
608
|
+
* (probably) rbx
|
609
|
+
|
610
|
+
Credits
|
611
|
+
-------
|
612
|
+
|
613
|
+
* Dan Kubb ([dkubb](https://github.com/dkubb))
|
614
|
+
* Chris Corbyn ([d11wtq](https://github.com/d11wtq))
|
615
|
+
* Emmanuel Gomez ([emmanuel](https://github.com/emmanuel))
|
616
|
+
* Fabio Rehm ([fgrehm](https://github.com/fgrehm))
|
617
|
+
* Ryan Closner ([rclosner](https://github.com/rclosner))
|
618
|
+
* Markus Schirp ([mbj](https://github.com/mbj))
|
619
|
+
* Yves Senn ([senny](https://github.com/senny))
|
620
|
+
|
621
|
+
Contributing
|
622
|
+
-------------
|
623
|
+
|
624
|
+
* Fork the project.
|
625
|
+
* Make your feature addition or bug fix.
|
626
|
+
* Add tests for it. This is important so I don't break it in a
|
627
|
+
future version unintentionally.
|
628
|
+
* Commit, do not mess with Rakefile or version
|
629
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
630
|
+
* Send me a pull request. Bonus points for topic branches.
|