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
@@ -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