virtus2 2.0.1

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.
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
@@ -0,0 +1,51 @@
1
+ module Virtus
2
+ class Builder
3
+
4
+ # Context used for building "included" and "extended" hooks
5
+ #
6
+ # @private
7
+ class HookContext
8
+ attr_reader :builder, :config, :attribute_method
9
+
10
+ # @api private
11
+ def initialize(builder, config)
12
+ @builder, @config = builder, config
13
+ initialize_attribute_method
14
+ end
15
+
16
+ # @api private
17
+ def modules
18
+ modules = builder.extensions
19
+ modules << Model::Constructor if constructor?
20
+ modules << Model::MassAssignment if mass_assignment?
21
+ modules
22
+ end
23
+
24
+ # @api private
25
+ def constructor?
26
+ config.constructor
27
+ end
28
+
29
+ # @api private
30
+ def mass_assignment?
31
+ config.mass_assignment
32
+ end
33
+
34
+ # @api private
35
+ def finalize?
36
+ config.finalize
37
+ end
38
+
39
+ # @api private
40
+ def initialize_attribute_method
41
+ method_options = builder.options
42
+
43
+ @attribute_method = lambda do |name, type = nil, options = {}|
44
+ super(name, type, method_options.merge(options))
45
+ end
46
+ end
47
+
48
+ end # HookContext
49
+
50
+ end # Builder
51
+ end # Virtus
@@ -0,0 +1,133 @@
1
+ module Virtus
2
+
3
+ # Class to build a Virtus module with it's own config
4
+ #
5
+ # This allows for individual Virtus modules to be included in
6
+ # classes and not impacted by the global Virtus config,
7
+ # which is implemented using Virtus::config.
8
+ #
9
+ # @private
10
+ class Builder
11
+
12
+ # Return module
13
+ #
14
+ # @return [Module]
15
+ #
16
+ # @api private
17
+ attr_reader :mod
18
+
19
+ # Return config
20
+ #
21
+ # @return [config]
22
+ #
23
+ # @api private
24
+ attr_reader :config
25
+
26
+ # @api private
27
+ def self.call(options, &block)
28
+ new(Configuration.new(options, &block)).mod
29
+ end
30
+
31
+ # @api private
32
+ def self.pending
33
+ @pending ||= []
34
+ end
35
+
36
+ # Initializes a new Builder
37
+ #
38
+ # @param [Configuration] config
39
+ # @param [Module] mod
40
+ #
41
+ # @return [undefined]
42
+ #
43
+ # @api private
44
+ def initialize(conf, mod = Module.new)
45
+ @config, @mod = conf, mod
46
+ add_included_hook
47
+ add_extended_hook
48
+ end
49
+
50
+ # @api private
51
+ def extensions
52
+ [Model::Core]
53
+ end
54
+
55
+ # @api private
56
+ def options
57
+ config.to_h
58
+ end
59
+
60
+ private
61
+
62
+ # Adds the .included hook to the anonymous module which then defines the
63
+ # .attribute method to override the default.
64
+ #
65
+ # @return [Module]
66
+ #
67
+ # @api private
68
+ def add_included_hook
69
+ with_hook_context do |context|
70
+ mod.define_singleton_method :included do |object|
71
+ Builder.pending << object unless context.finalize?
72
+ context.modules.each { |mod| object.send(:include, mod) }
73
+ object.define_singleton_method(:attribute, context.attribute_method)
74
+ end
75
+ end
76
+ end
77
+
78
+ # @api private
79
+ def add_extended_hook
80
+ with_hook_context do |context|
81
+ mod.define_singleton_method :extended do |object|
82
+ context.modules.each { |mod| object.extend(mod) }
83
+ object.define_singleton_method(:attribute, context.attribute_method)
84
+ end
85
+ end
86
+ end
87
+
88
+ # @api private
89
+ def with_hook_context
90
+ yield(HookContext.new(self, config))
91
+ end
92
+
93
+ end # class Builder
94
+
95
+ # @private
96
+ class ModelBuilder < Builder
97
+ end # ModelBuilder
98
+
99
+ # @private
100
+ class ModuleBuilder < Builder
101
+
102
+ private
103
+
104
+ # @api private
105
+ def add_included_hook
106
+ with_hook_context do |context|
107
+ mod.define_singleton_method :included do |object|
108
+ super(object)
109
+ object.extend(ModuleExtensions)
110
+ ModuleExtensions.setup(object, context.modules)
111
+ object.define_singleton_method(:attribute, context.attribute_method)
112
+ end
113
+ end
114
+ end
115
+
116
+ end # ModuleBuilder
117
+
118
+ # @private
119
+ class ValueObjectBuilder < Builder
120
+
121
+ # @api private
122
+ def extensions
123
+ super << ValueObject::AllowedWriterMethods << ValueObject::InstanceMethods
124
+ end
125
+
126
+ # @api private
127
+ def options
128
+ super.merge(:writer => :private)
129
+ end
130
+
131
+ end # ValueObjectBuilder
132
+
133
+ end # module Virtus
@@ -0,0 +1,48 @@
1
+ module Virtus
2
+
3
+ # Class-level extensions
4
+ module ClassInclusions
5
+
6
+ # Extends a descendant with class and instance methods
7
+ #
8
+ # @param [Class] descendant
9
+ #
10
+ # @return [undefined]
11
+ #
12
+ # @api private
13
+ def self.included(descendant)
14
+ super
15
+ descendant.extend(ClassMethods)
16
+ descendant.class_eval { include Methods }
17
+ descendant.class_eval { include InstanceMethods }
18
+ descendant.class_eval { include InstanceMethods::Constructor }
19
+ descendant.class_eval { include InstanceMethods::MassAssignment }
20
+ end
21
+ private_class_method :included
22
+
23
+ module Methods
24
+
25
+ # Return a list of allowed writer method names
26
+ #
27
+ # @return [Set]
28
+ #
29
+ # @api private
30
+ def allowed_writer_methods
31
+ self.class.allowed_writer_methods
32
+ end
33
+
34
+ private
35
+
36
+ # Return class' attribute set
37
+ #
38
+ # @return [Virtus::AttributeSet]
39
+ #
40
+ # @api private
41
+ def attribute_set
42
+ self.class.attribute_set
43
+ end
44
+
45
+ end # Methods
46
+
47
+ end # module ClassInclusions
48
+ end # module Virtus
@@ -0,0 +1,90 @@
1
+ module Virtus
2
+
3
+ # Class methods that are added when you include Virtus
4
+ module ClassMethods
5
+ include Extensions::Methods
6
+ include ConstMissingExtensions
7
+
8
+ # Hook called when module is extended
9
+ #
10
+ # @param [Class] descendant
11
+ #
12
+ # @return [undefined]
13
+ #
14
+ # @api private
15
+ def self.extended(descendant)
16
+ super
17
+ descendant.send(:include, AttributeSet.create(descendant))
18
+ end
19
+ private_class_method :extended
20
+
21
+ # Returns all the attributes defined on a Class
22
+ #
23
+ # @example
24
+ # class User
25
+ # include Virtus
26
+ #
27
+ # attribute :name, String
28
+ # attribute :age, Integer
29
+ # end
30
+ #
31
+ # User.attribute_set # =>
32
+ #
33
+ # TODO: implement inspect so the output is not cluttered - solnic
34
+ #
35
+ # @return [AttributeSet]
36
+ #
37
+ # @api public
38
+ def attribute_set
39
+ @attribute_set
40
+ end
41
+
42
+ # @see Virtus::ClassMethods.attribute_set
43
+ #
44
+ # @deprecated
45
+ #
46
+ # @api public
47
+ def attributes
48
+ warn "#{self}.attributes is deprecated. Use #{self}.attribute_set instead: #{caller.first}"
49
+ attribute_set
50
+ end
51
+
52
+ private
53
+
54
+ # Setup descendants' own Attribute-accessor-method-hosting modules
55
+ #
56
+ # Descendants inherit Attribute accessor methods via Ruby's inheritance
57
+ # mechanism: Attribute accessor methods are defined in a module included
58
+ # in a superclass. Attributes defined on descendants add methods to the
59
+ # descendant's Attributes accessor module, leaving the superclass's method
60
+ # table unaffected.
61
+ #
62
+ # @param [Class] descendant
63
+ #
64
+ # @return [undefined]
65
+ #
66
+ # @api private
67
+ def inherited(descendant)
68
+ super
69
+ AttributeSet.create(descendant)
70
+ descendant.module_eval { include attribute_set }
71
+ end
72
+
73
+ # The list of allowed public methods
74
+ #
75
+ # @return [Array<String>]
76
+ #
77
+ # @api private
78
+ def allowed_methods
79
+ public_instance_methods.map(&:to_s)
80
+ end
81
+
82
+ # @api private
83
+ def assert_valid_name(name)
84
+ if instance_methods.include?(:attributes) && name.to_sym == :attributes
85
+ raise ArgumentError, "#{name.inspect} is not allowed as an attribute name"
86
+ end
87
+ end
88
+
89
+ end # module ClassMethods
90
+ end # module Virtus
@@ -0,0 +1,41 @@
1
+ module Virtus
2
+
3
+ # Abstract coercer class
4
+ #
5
+ class Coercer
6
+ include Equalizer.new(inspect) << :primitive << :type
7
+
8
+ # @api private
9
+ attr_reader :primitive, :type
10
+
11
+ # @api private
12
+ def initialize(type)
13
+ @type = type
14
+ @primitive = type.primitive
15
+ end
16
+
17
+ # Coerce input value into expected primitive type
18
+ #
19
+ # @param [Object] input
20
+ #
21
+ # @return [Object] coerced input
22
+ #
23
+ # @api public
24
+ def call(input)
25
+ NotImplementedError.new("#{self.class}#call must be implemented")
26
+ end
27
+
28
+ # Return if the input value was successfuly coerced
29
+ #
30
+ # @param [Object] input
31
+ #
32
+ # @return [Object] coerced input
33
+ #
34
+ # @api public
35
+ def success?(primitive, input)
36
+ input.kind_of?(primitive)
37
+ end
38
+
39
+ end # Coercer
40
+
41
+ end # Virtus
@@ -0,0 +1,72 @@
1
+ module Virtus
2
+
3
+ # A Configuration instance
4
+ class Configuration
5
+
6
+ # Access the finalize setting for this instance
7
+ attr_accessor :finalize
8
+
9
+ # Access the coerce setting for this instance
10
+ attr_accessor :coerce
11
+
12
+ # Access the strict setting for this instance
13
+ attr_accessor :strict
14
+
15
+ # Access the nullify_blank setting for this instance
16
+ attr_accessor :nullify_blank
17
+
18
+ # Access the required setting for this instance
19
+ attr_accessor :required
20
+
21
+ # Access the constructor setting for this instance
22
+ attr_accessor :constructor
23
+
24
+ # Access the mass-assignment setting for this instance
25
+ attr_accessor :mass_assignment
26
+
27
+ # Initialized a configuration instance
28
+ #
29
+ # @return [undefined]
30
+ #
31
+ # @api private
32
+ def initialize(options={})
33
+ @finalize = options.fetch(:finalize, true)
34
+ @coerce = options.fetch(:coerce, true)
35
+ @strict = options.fetch(:strict, false)
36
+ @nullify_blank = options.fetch(:nullify_blank, false)
37
+ @required = options.fetch(:required, true)
38
+ @constructor = options.fetch(:constructor, true)
39
+ @mass_assignment = options.fetch(:mass_assignment, true)
40
+ @coercer = Coercible::Coercer.new
41
+
42
+ yield self if block_given?
43
+ end
44
+
45
+ # Access the coercer for this instance and optional configure a
46
+ # new coercer with the passed block
47
+ #
48
+ # @example
49
+ # configuration.coercer do |config|
50
+ # config.string.boolean_map = { true => '1', false => '0' }
51
+ # end
52
+ #
53
+ # @return [Coercer]
54
+ #
55
+ # @api private
56
+ def coercer(&block)
57
+ @coercer = Coercible::Coercer.new(&block) if block_given?
58
+ @coercer
59
+ end
60
+
61
+ # @api private
62
+ def to_h
63
+ { :coerce => coerce,
64
+ :finalize => finalize,
65
+ :strict => strict,
66
+ :nullify_blank => nullify_blank,
67
+ :required => required,
68
+ :configured_coercer => coercer }.freeze
69
+ end
70
+
71
+ end # class Configuration
72
+ end # module Virtus
@@ -0,0 +1,18 @@
1
+ module Virtus
2
+ module ConstMissingExtensions
3
+
4
+ # Hooks into const missing process to determine types of attributes
5
+ #
6
+ # @param [String] name
7
+ #
8
+ # @return [Class]
9
+ #
10
+ # @api private
11
+ def const_missing(name)
12
+ Attribute::Builder.determine_type(name) or
13
+ Axiom::Types.const_defined?(name) && Axiom::Types.const_get(name) or
14
+ super
15
+ end
16
+
17
+ end
18
+ end