motion_virtus 1.0.0.beta0

Sign up to get free protection for your applications and to get access to all the features.
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