virtus2 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +39 -0
  3. data/.rspec +2 -0
  4. data/.yardopts +1 -0
  5. data/CONTRIBUTING.md +18 -0
  6. data/Changelog.md +258 -0
  7. data/Gemfile +10 -0
  8. data/Guardfile +19 -0
  9. data/LICENSE +20 -0
  10. data/README.md +630 -0
  11. data/Rakefile +15 -0
  12. data/TODO.md +6 -0
  13. data/lib/virtus/attribute/accessor.rb +103 -0
  14. data/lib/virtus/attribute/boolean.rb +55 -0
  15. data/lib/virtus/attribute/builder.rb +182 -0
  16. data/lib/virtus/attribute/coercer.rb +45 -0
  17. data/lib/virtus/attribute/coercible.rb +20 -0
  18. data/lib/virtus/attribute/collection.rb +103 -0
  19. data/lib/virtus/attribute/default_value/from_callable.rb +35 -0
  20. data/lib/virtus/attribute/default_value/from_clonable.rb +35 -0
  21. data/lib/virtus/attribute/default_value/from_symbol.rb +35 -0
  22. data/lib/virtus/attribute/default_value.rb +51 -0
  23. data/lib/virtus/attribute/embedded_value.rb +67 -0
  24. data/lib/virtus/attribute/enum.rb +45 -0
  25. data/lib/virtus/attribute/hash.rb +130 -0
  26. data/lib/virtus/attribute/lazy_default.rb +18 -0
  27. data/lib/virtus/attribute/nullify_blank.rb +24 -0
  28. data/lib/virtus/attribute/strict.rb +26 -0
  29. data/lib/virtus/attribute.rb +245 -0
  30. data/lib/virtus/attribute_set.rb +240 -0
  31. data/lib/virtus/builder/hook_context.rb +51 -0
  32. data/lib/virtus/builder.rb +133 -0
  33. data/lib/virtus/class_inclusions.rb +48 -0
  34. data/lib/virtus/class_methods.rb +90 -0
  35. data/lib/virtus/coercer.rb +41 -0
  36. data/lib/virtus/configuration.rb +72 -0
  37. data/lib/virtus/const_missing_extensions.rb +18 -0
  38. data/lib/virtus/extensions.rb +105 -0
  39. data/lib/virtus/instance_methods.rb +218 -0
  40. data/lib/virtus/model.rb +68 -0
  41. data/lib/virtus/module_extensions.rb +88 -0
  42. data/lib/virtus/support/equalizer.rb +128 -0
  43. data/lib/virtus/support/options.rb +113 -0
  44. data/lib/virtus/support/type_lookup.rb +109 -0
  45. data/lib/virtus/value_object.rb +150 -0
  46. data/lib/virtus/version.rb +3 -0
  47. data/lib/virtus.rb +310 -0
  48. data/spec/integration/attributes_attribute_spec.rb +28 -0
  49. data/spec/integration/building_module_spec.rb +90 -0
  50. data/spec/integration/collection_member_coercion_spec.rb +96 -0
  51. data/spec/integration/custom_attributes_spec.rb +42 -0
  52. data/spec/integration/custom_collection_attributes_spec.rb +101 -0
  53. data/spec/integration/default_values_spec.rb +87 -0
  54. data/spec/integration/defining_attributes_spec.rb +86 -0
  55. data/spec/integration/embedded_value_spec.rb +50 -0
  56. data/spec/integration/extending_objects_spec.rb +35 -0
  57. data/spec/integration/hash_attributes_coercion_spec.rb +54 -0
  58. data/spec/integration/inheritance_spec.rb +42 -0
  59. data/spec/integration/injectible_coercers_spec.rb +48 -0
  60. data/spec/integration/mass_assignment_with_accessors_spec.rb +44 -0
  61. data/spec/integration/overriding_virtus_spec.rb +46 -0
  62. data/spec/integration/required_attributes_spec.rb +25 -0
  63. data/spec/integration/struct_as_embedded_value_spec.rb +28 -0
  64. data/spec/integration/using_modules_spec.rb +55 -0
  65. data/spec/integration/value_object_with_custom_constructor_spec.rb +42 -0
  66. data/spec/integration/virtus/instance_level_attributes_spec.rb +23 -0
  67. data/spec/integration/virtus/value_object_spec.rb +99 -0
  68. data/spec/shared/constants_helpers.rb +9 -0
  69. data/spec/shared/freeze_method_behavior.rb +40 -0
  70. data/spec/shared/idempotent_method_behaviour.rb +5 -0
  71. data/spec/shared/options_class_method.rb +19 -0
  72. data/spec/spec_helper.rb +41 -0
  73. data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +43 -0
  74. data/spec/unit/virtus/attribute/boolean/value_coerced_predicate_spec.rb +25 -0
  75. data/spec/unit/virtus/attribute/class_methods/build_spec.rb +180 -0
  76. data/spec/unit/virtus/attribute/class_methods/coerce_spec.rb +32 -0
  77. data/spec/unit/virtus/attribute/coerce_spec.rb +129 -0
  78. data/spec/unit/virtus/attribute/coercible_predicate_spec.rb +20 -0
  79. data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +105 -0
  80. data/spec/unit/virtus/attribute/collection/coerce_spec.rb +74 -0
  81. data/spec/unit/virtus/attribute/collection/value_coerced_predicate_spec.rb +31 -0
  82. data/spec/unit/virtus/attribute/comparison_spec.rb +20 -0
  83. data/spec/unit/virtus/attribute/custom_collection_spec.rb +29 -0
  84. data/spec/unit/virtus/attribute/defined_spec.rb +20 -0
  85. data/spec/unit/virtus/attribute/embedded_value/class_methods/build_spec.rb +70 -0
  86. data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +91 -0
  87. data/spec/unit/virtus/attribute/get_spec.rb +32 -0
  88. data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +106 -0
  89. data/spec/unit/virtus/attribute/hash/coerce_spec.rb +92 -0
  90. data/spec/unit/virtus/attribute/lazy_predicate_spec.rb +20 -0
  91. data/spec/unit/virtus/attribute/rename_spec.rb +16 -0
  92. data/spec/unit/virtus/attribute/required_predicate_spec.rb +19 -0
  93. data/spec/unit/virtus/attribute/set_default_value_spec.rb +107 -0
  94. data/spec/unit/virtus/attribute/set_spec.rb +29 -0
  95. data/spec/unit/virtus/attribute/value_coerced_predicate_spec.rb +19 -0
  96. data/spec/unit/virtus/attribute_set/append_spec.rb +47 -0
  97. data/spec/unit/virtus/attribute_set/define_reader_method_spec.rb +36 -0
  98. data/spec/unit/virtus/attribute_set/define_writer_method_spec.rb +36 -0
  99. data/spec/unit/virtus/attribute_set/each_spec.rb +65 -0
  100. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +17 -0
  101. data/spec/unit/virtus/attribute_set/element_set_spec.rb +64 -0
  102. data/spec/unit/virtus/attribute_set/merge_spec.rb +34 -0
  103. data/spec/unit/virtus/attribute_set/reset_spec.rb +71 -0
  104. data/spec/unit/virtus/attribute_spec.rb +229 -0
  105. data/spec/unit/virtus/attributes_reader_spec.rb +41 -0
  106. data/spec/unit/virtus/attributes_writer_spec.rb +51 -0
  107. data/spec/unit/virtus/class_methods/finalize_spec.rb +67 -0
  108. data/spec/unit/virtus/class_methods/new_spec.rb +39 -0
  109. data/spec/unit/virtus/config_spec.rb +13 -0
  110. data/spec/unit/virtus/element_reader_spec.rb +21 -0
  111. data/spec/unit/virtus/element_writer_spec.rb +19 -0
  112. data/spec/unit/virtus/freeze_spec.rb +41 -0
  113. data/spec/unit/virtus/model_spec.rb +197 -0
  114. data/spec/unit/virtus/module_spec.rb +174 -0
  115. data/spec/unit/virtus/set_default_attributes_spec.rb +32 -0
  116. data/spec/unit/virtus/value_object_spec.rb +138 -0
  117. data/virtus2.gemspec +26 -0
  118. 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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --order random
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
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'dry-inflector'
6
+ gem 'rspec'
7
+ gem 'bogus'
8
+ gem 'simplecov', platform: :ruby
9
+
10
+ gem "codeclimate-test-reporter", group: :test, require: false
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.