motion_virtus 1.0.0.beta0

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 (59) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +445 -0
  3. data/lib/motion_virtus.rb +13 -0
  4. data/lib/project/attribute/accessor/builder.rb +69 -0
  5. data/lib/project/attribute/accessor/lazy_accessor.rb +39 -0
  6. data/lib/project/attribute/accessor.rb +100 -0
  7. data/lib/project/attribute/accessor_method.rb +73 -0
  8. data/lib/project/attribute/array.rb +24 -0
  9. data/lib/project/attribute/boolean.rb +52 -0
  10. data/lib/project/attribute/class.rb +23 -0
  11. data/lib/project/attribute/coercer.rb +43 -0
  12. data/lib/project/attribute/collection/coercible_writer.rb +83 -0
  13. data/lib/project/attribute/collection.rb +56 -0
  14. data/lib/project/attribute/date.rb +36 -0
  15. data/lib/project/attribute/date_time.rb +38 -0
  16. data/lib/project/attribute/decimal.rb +23 -0
  17. data/lib/project/attribute/default_value/from_callable.rb +37 -0
  18. data/lib/project/attribute/default_value/from_clonable.rb +37 -0
  19. data/lib/project/attribute/default_value/from_symbol.rb +37 -0
  20. data/lib/project/attribute/default_value.rb +49 -0
  21. data/lib/project/attribute/embedded_value/open_struct_coercer.rb +43 -0
  22. data/lib/project/attribute/embedded_value/struct_coercer.rb +42 -0
  23. data/lib/project/attribute/embedded_value.rb +69 -0
  24. data/lib/project/attribute/float.rb +30 -0
  25. data/lib/project/attribute/hash/coercible_writer.rb +78 -0
  26. data/lib/project/attribute/hash.rb +66 -0
  27. data/lib/project/attribute/integer.rb +27 -0
  28. data/lib/project/attribute/numeric.rb +25 -0
  29. data/lib/project/attribute/object.rb +13 -0
  30. data/lib/project/attribute/reader.rb +39 -0
  31. data/lib/project/attribute/set.rb +22 -0
  32. data/lib/project/attribute/string.rb +24 -0
  33. data/lib/project/attribute/symbol.rb +23 -0
  34. data/lib/project/attribute/time.rb +36 -0
  35. data/lib/project/attribute/writer/coercible.rb +45 -0
  36. data/lib/project/attribute/writer.rb +73 -0
  37. data/lib/project/attribute.rb +292 -0
  38. data/lib/project/attribute_set.rb +260 -0
  39. data/lib/project/class_inclusions.rb +41 -0
  40. data/lib/project/class_methods.rb +102 -0
  41. data/lib/project/configuration.rb +65 -0
  42. data/lib/project/const_missing_extensions.rb +16 -0
  43. data/lib/project/extensions.rb +101 -0
  44. data/lib/project/instance_methods.rb +165 -0
  45. data/lib/project/module_builder.rb +92 -0
  46. data/lib/project/module_extensions.rb +72 -0
  47. data/lib/project/stubs/date.rb +2 -0
  48. data/lib/project/stubs/date_time.rb +2 -0
  49. data/lib/project/stubs/decimal.rb +2 -0
  50. data/lib/project/stubs/ostruct.rb +149 -0
  51. data/lib/project/stubs/set.rb +767 -0
  52. data/lib/project/stubs.rb +5 -0
  53. data/lib/project/support/equalizer.rb +147 -0
  54. data/lib/project/support/options.rb +114 -0
  55. data/lib/project/support/type_lookup.rb +109 -0
  56. data/lib/project/value_object.rb +139 -0
  57. data/lib/project/version.rb +3 -0
  58. data/lib/project/virtus.rb +128 -0
  59. metadata +158 -0
@@ -0,0 +1,73 @@
1
+ module Virtus
2
+ class Attribute
3
+
4
+ # Abstract accessor method class
5
+ #
6
+ # @api public
7
+ #
8
+ # @abstract
9
+ class AccessorMethod
10
+ #include Adamantium::Flat
11
+
12
+ #include AbstractType
13
+
14
+ #abstract_method :call
15
+ def call(*)
16
+ raise NotImplementedError, "#{self.class.inspect}##{name} is not implemented"
17
+ end
18
+
19
+ #abstract_method :define_method
20
+ def call(*)
21
+ raise NotImplementedError, "#{self.class.inspect}##{name} is not implemented"
22
+ end
23
+
24
+
25
+ # Return name
26
+ #
27
+ # @return [Symbol]
28
+ #
29
+ # @api private
30
+ attr_reader :name
31
+
32
+ # Return visibility
33
+ #
34
+ # @return [Symbol]
35
+ #
36
+ # @api private
37
+ attr_reader :visibility
38
+
39
+ # Return instance variable name
40
+ #
41
+ # @return [Symbol]
42
+ #
43
+ # @api private
44
+ attr_reader :instance_variable_name
45
+
46
+ # Initialize accessor method instance
47
+ #
48
+ # @param [#to_sym] name
49
+ #
50
+ # @param [Hash] options
51
+ #
52
+ # @return [undefined]
53
+ #
54
+ # @api private
55
+ def initialize(name, options = {})
56
+ @name = name.to_sym
57
+ @visibility = options.fetch(:visibility, :public)
58
+ @instance_variable_name = "@#{name}".to_sym
59
+ end
60
+
61
+ # Return if the accessor is public
62
+ #
63
+ # @return [TrueClass,FalseClass]
64
+ #
65
+ # @api private
66
+ def public?
67
+ visibility == :public
68
+ end
69
+
70
+ end # class AccessorMethod
71
+
72
+ end # class Attribute
73
+ end # module Virtus
@@ -0,0 +1,24 @@
1
+ motion_require 'collection.rb'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Array
7
+ #
8
+ # @example
9
+ # class Post
10
+ # include Virtus
11
+ #
12
+ # attribute :tags, Array
13
+ # end
14
+ #
15
+ # post = Post.new(:tags => %w(red green blue))
16
+ #
17
+ class Array < Collection
18
+ primitive ::Array
19
+ coercion_method :to_array
20
+ default primitive.new
21
+
22
+ end # class Array
23
+ end # class Attribute
24
+ end # module Virtus
@@ -0,0 +1,52 @@
1
+ module Virtus
2
+ class Attribute
3
+
4
+ # Boolean attribute allows true or false values to be set
5
+ # Additionally it adds boolean reader method, like "admin?"
6
+ #
7
+ # @example
8
+ # class Post
9
+ # include Virtus
10
+ #
11
+ # attribute :published, Boolean
12
+ # end
13
+ #
14
+ # post = Post.new(:published => false)
15
+ # post.published? # => false
16
+ #
17
+ class Boolean < Object
18
+ primitive TrueClass
19
+ coercion_method :to_boolean
20
+
21
+ # Returns if the given value is either true or false
22
+ #
23
+ # @example
24
+ # boolean = Virtus::Attribute::Boolean.new(:bool)
25
+ # boolean.value_coerced?(true) # => true
26
+ # boolean.value_coerced?(false) # => true
27
+ # boolean.value_coerced?(1) # => false
28
+ # boolean.value_coerced?('true') # => false
29
+ #
30
+ # @return [Boolean]
31
+ #
32
+ # @api public
33
+ def value_coerced?(value)
34
+ value.equal?(true) || value.equal?(false)
35
+ end
36
+
37
+ # Creates an attribute reader method as a query
38
+ #
39
+ # @param [Module] mod
40
+ #
41
+ # @return [self]
42
+ #
43
+ # @api private
44
+ def define_accessor_methods(mod)
45
+ super
46
+ mod.define_reader_method(accessor, "#{name}?", reader.visibility)
47
+ self
48
+ end
49
+
50
+ end # class Boolean
51
+ end # class Attribute
52
+ end # module Virtus
@@ -0,0 +1,23 @@
1
+ motion_require 'object'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Class
7
+ #
8
+ # @example
9
+ # class Entity
10
+ # include Virtus
11
+ #
12
+ # attribute :model, Class
13
+ # end
14
+ #
15
+ # post = Entity.new(:model => Model)
16
+ #
17
+ class Class < Object
18
+ primitive ::Class
19
+ coercion_method :to_constant
20
+
21
+ end # class Class
22
+ end # class Attribute
23
+ end # module Virtus
@@ -0,0 +1,43 @@
1
+ module Virtus
2
+ class Attribute
3
+
4
+ # Coercer accessor wrapper
5
+ #
6
+ # @api private
7
+ class Coercer
8
+
9
+ # Initialize a new coercer object
10
+ #
11
+ # @param [Object] coercers accessor
12
+ # @param [Symbol] coercion method
13
+ #
14
+ # @return [undefined]
15
+ #
16
+ # @api private
17
+ def initialize(coercers, method)
18
+ @coercers = coercers
19
+ @method = method
20
+ end
21
+
22
+ # Coerce given value
23
+ #
24
+ # @return [Object]
25
+ #
26
+ # @api private
27
+ def call(value)
28
+ self[value.class].public_send(@method, value)
29
+ end
30
+
31
+ # Return coercer object for the given type
32
+ #
33
+ # @return [Object]
34
+ #
35
+ # @api private
36
+ def [](type)
37
+ @coercers[type]
38
+ end
39
+
40
+ end # class Coercer
41
+
42
+ end # class Attribute
43
+ end # module Virtus
@@ -0,0 +1,83 @@
1
+ module Virtus
2
+ class Attribute
3
+ class Collection
4
+
5
+ # Coercible writer for collection attributes
6
+ #
7
+ class CoercibleWriter < Attribute::Writer::Coercible
8
+
9
+ # Return member type
10
+ #
11
+ # @return [Class]
12
+ #
13
+ # @api private
14
+ attr_reader :member_type
15
+
16
+ # Return writer for collection members
17
+ #
18
+ #
19
+ # @return [Writer::Coercible]
20
+ #
21
+ # @api private
22
+ attr_reader :member_coercer
23
+
24
+ # Initialize a new writer instance
25
+ #
26
+ # @param [Symbol] name
27
+ #
28
+ # @param [::Hash] options
29
+ #
30
+ # @api private
31
+ def initialize(name, options)
32
+ super
33
+ @member_type = options.fetch(:member_type, ::Object)
34
+ @member_coercer = Attribute.determine_type(@member_type).coercer(@member_type)
35
+ end
36
+
37
+ # Coerce a collection with members
38
+ #
39
+ # @param [Object] value
40
+ #
41
+ # @return [Object]
42
+ #
43
+ # @api private
44
+ def coerce(_value)
45
+ coerced = super
46
+ return coerced unless coerced.respond_to?(:each_with_object)
47
+ coerced.each_with_object(new_collection) do |entry, collection|
48
+ coerce_and_append_member(collection, entry)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ # Return an instance of the collection
55
+ #
56
+ # @return [Enumerable]
57
+ #
58
+ # @api private
59
+ def new_collection
60
+ primitive.new
61
+ end
62
+
63
+ # Coerce a member of a source collection and append it to the target collection
64
+ #
65
+ # @param [Array, Set] collection
66
+ # target collection to which the coerced member should be appended
67
+ #
68
+ # @param [Object] entry
69
+ # the member that should be coerced and appended
70
+ #
71
+ # @return [Array, Set]
72
+ # collection with the coerced member appended to it
73
+ #
74
+ # @api private
75
+ def coerce_and_append_member(collection, entry)
76
+ collection << member_coercer.call(entry)
77
+ end
78
+
79
+ end # class CoercibleWriter
80
+
81
+ end # class Collection
82
+ end # class Attribute
83
+ end # module Virtus
@@ -0,0 +1,56 @@
1
+ motion_require 'object'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Abstract superclass for collection Attributes.
7
+ #
8
+ # Handles coercing members to the designated member type.
9
+ #
10
+ # @abstract
11
+ class Collection < Object
12
+
13
+ # Handles collection with member_type syntax
14
+ #
15
+ # @param [Class] type
16
+ #
17
+ # @param [Hash] options
18
+ #
19
+ # @return [Hash]
20
+ #
21
+ # @api private
22
+ def self.merge_options(type, _options)
23
+ merged_options = super
24
+
25
+ if !type.respond_to?(:count)
26
+ merged_options
27
+ elsif type.count > 1
28
+ raise NotImplementedError, "build SumType from list of types (#{type.inspect})"
29
+ else
30
+ merged_options.merge!(:member_type => type.first)
31
+ end
32
+
33
+ merged_options
34
+ end
35
+
36
+ # @see Virtus::Attribute.coercible_writer_class
37
+ #
38
+ # @return [::Class]
39
+ #
40
+ # @api private
41
+ def self.coercible_writer_class(_type, options)
42
+ options[:member_type] ? CoercibleWriter : super
43
+ end
44
+
45
+ # @see Virtus::Attribute.writer_option_names
46
+ #
47
+ # @return [Array<Symbol>]
48
+ #
49
+ # @api private
50
+ def self.writer_option_names
51
+ super << :member_type
52
+ end
53
+
54
+ end # class Collection
55
+ end # class Attribute
56
+ end # module Virtus
@@ -0,0 +1,36 @@
1
+ motion_require 'object'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Date
7
+ #
8
+ # @example
9
+ # class Post
10
+ # include Virtus
11
+ #
12
+ # attribute :published_on, Date
13
+ # end
14
+ #
15
+ # Post.new(:published_on => Date.today)
16
+ #
17
+ # # typecasting from a string
18
+ # Post.new(:published_on => '2011/06/09')
19
+ #
20
+ # # typecasting from a hash
21
+ # Post.new(:published_on => {
22
+ # :year => 2011,
23
+ # :month => 6,
24
+ # :day => 9,
25
+ # })
26
+ #
27
+ # # typecasting from an object which implements #to_date
28
+ # Post.new(:published_on => DateTime.now)
29
+ #
30
+ class Date < Object
31
+ primitive ::Date
32
+ coercion_method :to_date
33
+
34
+ end # class Date
35
+ end # class Attribute
36
+ end # module Virtus
@@ -0,0 +1,38 @@
1
+ motion_require 'object'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # DateTime
7
+ #
8
+ # @example
9
+ # class Post
10
+ # include Virtus
11
+ #
12
+ # attribute :published_at, DateTime
13
+ # end
14
+ #
15
+ # Post.new(:published_at => DateTime.now)
16
+ #
17
+ # # typecasting from a string
18
+ # Post.new(:published_on => '2011/06/09 10:48')
19
+ #
20
+ # # typecasting from a hash
21
+ # Post.new(:published_on => {
22
+ # :year => 2011,
23
+ # :month => 6,
24
+ # :day => 9,
25
+ # :hour => 10,
26
+ # :min => 48,
27
+ # })
28
+ #
29
+ # # typecasting from an object which implements #to_datetime
30
+ # Post.new(:published_on => Time.now)
31
+ #
32
+ class DateTime < Object
33
+ primitive ::DateTime
34
+ coercion_method :to_datetime
35
+
36
+ end # class DateTim
37
+ end # class Attribute
38
+ end # module Virtus
@@ -0,0 +1,23 @@
1
+ motion_require 'object'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Decimal
7
+ #
8
+ # @example
9
+ # class ExchangeRate
10
+ # include Virtus
11
+ #
12
+ # attribute :dollar, Decimal
13
+ # end
14
+ #
15
+ # ExchangeRate.new(:dollar => '2.6948')
16
+ #
17
+ class Decimal < Numeric
18
+ primitive ::BigDecimal
19
+ coercion_method :to_decimal
20
+
21
+ end # class Decimal
22
+ end # class Attribute
23
+ end # module Virtus
@@ -0,0 +1,37 @@
1
+ motion_require 'from_clonable'
2
+
3
+ module Virtus
4
+ class Attribute
5
+ class DefaultValue
6
+
7
+ # Represents default value evaluated via a callable object
8
+ #
9
+ # @api private
10
+ class FromCallable < DefaultValue
11
+
12
+ # Return if the class can handle the value
13
+ #
14
+ # @param [Object] value
15
+ #
16
+ # @return [Boolean]
17
+ #
18
+ # @api private
19
+ def self.handle?(value)
20
+ value.respond_to?(:call)
21
+ end
22
+
23
+ # Evaluates the value via value#call
24
+ #
25
+ # @param [Object] args
26
+ #
27
+ # @return [Object] evaluated value
28
+ #
29
+ # @api private
30
+ def call(*args)
31
+ @value.call(*args)
32
+ end
33
+
34
+ end # class FromCallable
35
+ end # class DefaultValue
36
+ end # class Attribute
37
+ end # module Virtus
@@ -0,0 +1,37 @@
1
+ motion_require '../default_value.rb'
2
+
3
+ module Virtus
4
+ class Attribute
5
+ class DefaultValue
6
+
7
+ # Represents default value evaluated via a clonable object
8
+ #
9
+ # @api private
10
+ class FromClonable < DefaultValue
11
+ SINGLETON_CLASSES = [
12
+ ::NilClass, ::TrueClass, ::FalseClass, ::Numeric, ::Symbol ].freeze
13
+
14
+ # Return if the class can handle the value
15
+ #
16
+ # @param [Object] value
17
+ #
18
+ # @return [Boolean]
19
+ #
20
+ # @api private
21
+ def self.handle?(value)
22
+ SINGLETON_CLASSES.none? {|c| value.is_a?(c) }
23
+ end
24
+
25
+ # Evaluates the value via value#clone
26
+ #
27
+ # @return [Object] evaluated value
28
+ #
29
+ # @api private
30
+ def call(*)
31
+ @value.clone
32
+ end
33
+
34
+ end # class FromClonable
35
+ end # class DefaultValue
36
+ end # class Attribute
37
+ end # module Virtus
@@ -0,0 +1,37 @@
1
+ motion_require 'from_callable'
2
+
3
+ module Virtus
4
+ class Attribute
5
+ class DefaultValue
6
+
7
+ # Represents default value evaluated via a symbol
8
+ #
9
+ # @api private
10
+ class FromSymbol < DefaultValue
11
+
12
+ # Return if the class can handle the value
13
+ #
14
+ # @param [Object] value
15
+ #
16
+ # @return [Boolean]
17
+ #
18
+ # @api private
19
+ def self.handle?(value)
20
+ value.is_a?(::Symbol)
21
+ end
22
+
23
+ # Evaluates the value via instance#public_send(value)
24
+ #
25
+ # Symbol value is returned if the instance doesn't respond to value
26
+ #
27
+ # @return [Object] evaluated value
28
+ #
29
+ # @api private
30
+ def call(instance, *)
31
+ instance.respond_to?(@value, true) ? instance.send(@value) : @value
32
+ end
33
+
34
+ end # class FromSymbol
35
+ end # class DefaultValue
36
+ end # class Attribute
37
+ end # module Virtus
@@ -0,0 +1,49 @@
1
+ module Virtus
2
+ class Attribute
3
+
4
+ # Class representing the default value option
5
+ #
6
+ # @api private
7
+ class DefaultValue
8
+ extend DescendantsTracker
9
+
10
+ # Builds a default value instance
11
+ #
12
+ # @return [Virtus::Attribute::DefaultValue]
13
+ #
14
+ # @api private
15
+ def self.build(*args)
16
+ klass = descendants.detect { |descendant| descendant.handle?(*args) } || self
17
+ klass.new(*args)
18
+ end
19
+
20
+ # Returns the value instance
21
+ #
22
+ # @return [Object]
23
+ #
24
+ # @api private
25
+ attr_reader :value
26
+
27
+ # Initializes an default value instance
28
+ #
29
+ # @param [Object] value
30
+ #
31
+ # @return [undefined]
32
+ #
33
+ # @api private
34
+ def initialize(value)
35
+ @value = value
36
+ end
37
+
38
+ # Evaluates the value
39
+ #
40
+ # @return [Object] evaluated value
41
+ #
42
+ # @api private
43
+ def call(*)
44
+ value
45
+ end
46
+
47
+ end # class DefaultValue
48
+ end # class Attribute
49
+ end # module Virtus
@@ -0,0 +1,43 @@
1
+ motion_require '../embedded_value.rb'
2
+
3
+ module Virtus
4
+ class Attribute
5
+ class EmbeddedValue < Object
6
+
7
+ # EmbeddedValue coercer handling OpenStruct primitive or Virtus object
8
+ #
9
+ class OpenStructCoercer
10
+
11
+ # Return primitive class
12
+ #
13
+ # @return [::Class]
14
+ #
15
+ # @api private
16
+ attr_reader :primitive
17
+
18
+ # Initialize coercer object
19
+ #
20
+ # @return [undefined]
21
+ #
22
+ # @api private
23
+ def initialize(primitive)
24
+ @primitive = primitive
25
+ end
26
+
27
+ # Build object from attribute hash
28
+ #
29
+ # @return [::Object]
30
+ #
31
+ # @api private
32
+ def call(attributes)
33
+ if attributes.kind_of?(primitive)
34
+ attributes
35
+ elsif not attributes.nil?
36
+ primitive.new(attributes)
37
+ end
38
+ end
39
+
40
+ end # class OpenStructCoercer
41
+ end # class EmbeddedValue
42
+ end # class Attribute
43
+ end # module Virtus
@@ -0,0 +1,42 @@
1
+ motion_require '../embedded_value.rb'
2
+
3
+ module Virtus
4
+ class Attribute
5
+ class EmbeddedValue < Object
6
+
7
+ # EmbeddedValue coercer handling Struct primitive
8
+ #
9
+ class StructCoercer
10
+ # Return primitive class
11
+ #
12
+ # @return [::Class]
13
+ #
14
+ # @api private
15
+ attr_reader :primitive
16
+
17
+ # Initialize coercer instance
18
+ #
19
+ # @return [undefined]
20
+ #
21
+ # @api private
22
+ def initialize(primitive)
23
+ @primitive = primitive
24
+ end
25
+
26
+ # Build a struct object from attributes
27
+ #
28
+ # @return [Struct]
29
+ #
30
+ # @api private
31
+ def call(attributes)
32
+ if attributes.kind_of?(primitive)
33
+ attributes
34
+ elsif not attributes.nil?
35
+ primitive.new(*attributes)
36
+ end
37
+ end
38
+
39
+ end # class StructCoercer
40
+ end # class EmbeddedValue
41
+ end # class Attribute
42
+ end # module Virtus