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,69 @@
1
+ motion_require 'object'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # EmbeddedValue
7
+ #
8
+ # @example
9
+ #
10
+ # class Address
11
+ # include Virtus
12
+ #
13
+ # attribute :street, String
14
+ # attribute :zipcode, String
15
+ # attribute :city, String
16
+ # end
17
+ #
18
+ # class User
19
+ # include Virtus
20
+ #
21
+ # attribute :address, Address
22
+ # end
23
+ #
24
+ # user = User.new(:address => {
25
+ # :street => 'Street 1/2', :zipcode => '12345', :city => 'NYC' })
26
+ #
27
+ class EmbeddedValue < Object
28
+ primitive ::OpenStruct
29
+
30
+ # @see Attribute.merge_options
31
+ #
32
+ # @return [Hash]
33
+ # an updated options hash for configuring an EmbeddedValue instance
34
+ #
35
+ # @api private
36
+ def self.merge_options(type, _options)
37
+ super.update(:primitive => type)
38
+ end
39
+
40
+ # Determine type based on class
41
+ #
42
+ # Virtus::EmbeddedValue.determine_type(Struct) # => Virtus::EmbeddedValue::FromStruct
43
+ # Virtus::EmbeddedValue.determine_type(VirtusClass) # => Virtus::EmbeddedValue::FromOpenStruct
44
+ #
45
+ # @param [Class] klass
46
+ #
47
+ # @return [Virtus::Attribute::EmbeddedValue]
48
+ #
49
+ # @api private
50
+ def self.determine_type(klass)
51
+ if klass <= Virtus || klass <= OpenStruct || klass <= Struct
52
+ self
53
+ end
54
+ end
55
+
56
+ # @api private
57
+ def self.coercer(type, _options = {})
58
+ if type <= Virtus || type <= OpenStruct
59
+ OpenStructCoercer.new(type)
60
+ elsif type <= Struct
61
+ StructCoercer.new(type)
62
+ else
63
+ super
64
+ end
65
+ end
66
+
67
+ end # class EmbeddedValue
68
+ end # class Attribute
69
+ end # module Virtus
@@ -0,0 +1,30 @@
1
+ module Virtus
2
+ class Attribute
3
+
4
+ # Float
5
+ #
6
+ # @example
7
+ # class ExchangeRate
8
+ # include Virtus
9
+ #
10
+ # attribute :dollar, Float
11
+ # end
12
+ #
13
+ # ExchangeRate.new(:dollar => 2.69)
14
+ #
15
+ # # typecasting from a string
16
+ # ExchangeRate.new(:dollar => '2.69')
17
+ #
18
+ # # typecasting from an integer
19
+ # ExchangeRate.new(:dollar => 2)
20
+ #
21
+ # # typecasting from an object which implements #to_f
22
+ # ExchangeRate.new(:dollar => BigDecimal.new('2.69')
23
+ #
24
+ class Float < Numeric
25
+ primitive ::Float
26
+ coercion_method :to_float
27
+
28
+ end # class Float
29
+ end # class Attribute
30
+ end # module Virtus
@@ -0,0 +1,78 @@
1
+ motion_require '../hash.rb'
2
+
3
+ module Virtus
4
+ class Attribute
5
+ class Hash
6
+
7
+ # Coercible writer for Hash attributes
8
+ #
9
+ class CoercibleWriter < Attribute::Writer::Coercible
10
+
11
+ # The type to which keys of this hash will be coerced
12
+ #
13
+ # @return [Virtus::Attribute]
14
+ #
15
+ # @api private
16
+ attr_reader :key_type
17
+
18
+ # The type to which values of this hash will be coerced
19
+ #
20
+ # @return [Virtus::Attribute]
21
+ #
22
+ # @api private
23
+ attr_reader :value_type
24
+
25
+ # Return writer for hash keys
26
+ #
27
+ # @return [Writer::Coercible]
28
+ #
29
+ # @api private
30
+ attr_reader :key_coercer
31
+
32
+ # Return writer for hash values
33
+ #
34
+ # @return [Writer::Coercible]
35
+ #
36
+ # @api private
37
+ attr_reader :value_coercer
38
+
39
+ # @api private
40
+ def initialize(name, options)
41
+ super
42
+ @key_type = options.fetch(:key_type, ::Object)
43
+ @value_type = options.fetch(:value_type, ::Object)
44
+ @key_coercer = Attribute.determine_type(@key_type).coercer(@key_type)
45
+ @value_coercer = Attribute.determine_type(@value_type).coercer(@value_type)
46
+ end
47
+
48
+ # Coerce a hash with keys and values
49
+ #
50
+ # @param [Object] value
51
+ #
52
+ # @return [Object]
53
+ #
54
+ # @api private
55
+ def coerce(_value)
56
+ coerced = super
57
+ return coerced unless coerced.respond_to?(:each_with_object)
58
+ coerced.each_with_object(new_hash) do |(key, value), hash|
59
+ hash[key_coercer.call(key)] = value_coercer.call(value)
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ # Return an instance of the hash
66
+ #
67
+ # @return [Hash]
68
+ #
69
+ # @api private
70
+ def new_hash
71
+ primitive.new
72
+ end
73
+
74
+ end # class CoercibleWriter
75
+
76
+ end # class Collection
77
+ end # class Attribute
78
+ end # module Virtus
@@ -0,0 +1,66 @@
1
+ motion_require 'object'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Hash
7
+ #
8
+ # @example
9
+ # class Post
10
+ # include Virtus
11
+ #
12
+ # attribute :meta, Hash
13
+ # end
14
+ #
15
+ # Post.new(:meta => { :tags => %w(foo bar) })
16
+ #
17
+ class Hash < Object
18
+ primitive ::Hash
19
+ coercion_method :to_hash
20
+ default primitive.new
21
+
22
+ # Handles hashes with [key_type => value_type] syntax
23
+ #
24
+ # @param [Class] type
25
+ #
26
+ # @param [Hash] options
27
+ #
28
+ # @return [Hash]
29
+ #
30
+ # @api private
31
+ def self.merge_options(type, _options)
32
+ merged_options = super
33
+
34
+ if !type.respond_to?(:size)
35
+ merged_options
36
+ elsif type.size > 1
37
+ raise ArgumentError, "more than one [key => value] pair in `#{type.inspect}`"
38
+ else
39
+ key_type, value_type = type.first
40
+ merged_options.merge!(:key_type => key_type, :value_type => value_type)
41
+ end
42
+
43
+ merged_options
44
+ end
45
+
46
+ # @see Virtus::Attribute.coercible_writer_class
47
+ #
48
+ # @return [::Class]
49
+ #
50
+ # @api private
51
+ def self.coercible_writer_class(_type, options)
52
+ options[:key_type] && options[:value_type] ? CoercibleWriter : super
53
+ end
54
+
55
+ # @see Virtus::Attribute.writer_option_names
56
+ #
57
+ # @return [Array<Symbol>]
58
+ #
59
+ # @api private
60
+ def self.writer_option_names
61
+ super << :key_type << :value_type
62
+ end
63
+
64
+ end # class Hash
65
+ end # class Attribute
66
+ end # module Virtus
@@ -0,0 +1,27 @@
1
+ module Virtus
2
+ class Attribute
3
+
4
+ # Integer
5
+ #
6
+ # @example
7
+ # class Post
8
+ # include Virtus
9
+ #
10
+ # attribute :read_count, Integer
11
+ # end
12
+ #
13
+ # Post.new(:read_count => 100)
14
+ #
15
+ # # typecasting from a string
16
+ # Post.new(:read_count => '100')
17
+ #
18
+ # # typecasting from an object that implements #to_i
19
+ # Post.new(:read_count => 100.0)
20
+ #
21
+ class Integer < Numeric
22
+ primitive ::Integer
23
+ coercion_method :to_integer
24
+
25
+ end # class Integer
26
+ end # class Attribute
27
+ end # module Virtus
@@ -0,0 +1,25 @@
1
+ motion_require 'object'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Base class for all numerical attributes
7
+ class Numeric < Object
8
+ primitive ::Numeric
9
+ #accept_options :min, :max
10
+
11
+ def self.min(value=Undefined)
12
+ return @min if value.equal?(Undefined)
13
+ @min = value
14
+ self
15
+ end
16
+
17
+ def self.max(value=Undefined)
18
+ return @max if value.equal?(Undefined)
19
+ @max = value
20
+ self
21
+ end
22
+
23
+ end # class Numeric
24
+ end # class Attribute
25
+ end # module Virtus
@@ -0,0 +1,13 @@
1
+ motion_require '../attribute.rb'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Base class for every attribute
7
+ class Object < Attribute
8
+ primitive ::Object
9
+ coercion_method :to_object
10
+
11
+ end # class Object
12
+ end # class Attribute
13
+ end # module Virtus
@@ -0,0 +1,39 @@
1
+ motion_require 'accessor_method'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Reader method object
7
+ #
8
+ # @api public
9
+ class Reader < AccessorMethod
10
+
11
+ # Returns value of an attribute for the given instance
12
+ #
13
+ # @example
14
+ # attribute.get(instance) # => value
15
+ #
16
+ # @return [Object]
17
+ # value of an attribute
18
+ #
19
+ # @api public
20
+ def call(instance)
21
+ instance.instance_variable_get(instance_variable_name)
22
+ end
23
+
24
+ # Creates an attribute reader method
25
+ #
26
+ # @param [Module] mod
27
+ #
28
+ # @return [self]
29
+ #
30
+ # @api private
31
+ def define_method(accessor, mod)
32
+ mod.define_reader_method(accessor, name, visibility)
33
+ self
34
+ end
35
+
36
+ end # class Reader
37
+
38
+ end # class Attribute
39
+ end # module Virtus
@@ -0,0 +1,22 @@
1
+ module Virtus
2
+ class Attribute
3
+
4
+ # Set
5
+ #
6
+ # @example
7
+ # class Post
8
+ # include Virtus
9
+ #
10
+ # attribute :tags, Set
11
+ # end
12
+ #
13
+ # post = Post.new(:tags => %w(red green blue))
14
+ #
15
+ class Set < Collection
16
+ primitive ::Set
17
+ coercion_method :to_set
18
+ default primitive.new
19
+
20
+ end # class Set
21
+ end # class Attribute
22
+ end # module Virtus
@@ -0,0 +1,24 @@
1
+ module Virtus
2
+ class Attribute
3
+
4
+ # String
5
+ #
6
+ # @example
7
+ # class User
8
+ # include Virtus
9
+ #
10
+ # attribute :name, String
11
+ # end
12
+ #
13
+ # User.new(:name => 'John')
14
+ #
15
+ # # typecasting from an object which implements #to_s
16
+ # User.new(:name => :John)
17
+ #
18
+ class String < Object
19
+ primitive ::String
20
+ coercion_method :to_string
21
+
22
+ end # class String
23
+ end # class Attribute
24
+ end # module Virtus
@@ -0,0 +1,23 @@
1
+ motion_require 'object'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Symbol
7
+ #
8
+ # @example
9
+ # class Product
10
+ # include Virtus
11
+ #
12
+ # attribute :code, Symbol
13
+ # end
14
+ #
15
+ # product = Product.new(:code => :red)
16
+ #
17
+ class Symbol < Object
18
+ primitive ::Symbol
19
+ coercion_method :to_symbol
20
+
21
+ end # class Symbol
22
+ end # class Attribute
23
+ end # module Virtus
@@ -0,0 +1,36 @@
1
+ module Virtus
2
+ class Attribute
3
+
4
+ # Time
5
+ #
6
+ # @example
7
+ # class Post
8
+ # include Virtus
9
+ #
10
+ # attribute :published_at, Time
11
+ # end
12
+ #
13
+ # Post.new(:published_at => Time.now)
14
+ #
15
+ # # typecasting from a string
16
+ # Post.new(:published_at => '2011/06/09 11:08')
17
+ #
18
+ # # typecasting from a hash
19
+ # Post.new(:published_at => {
20
+ # :year => 2011,
21
+ # :month => 6,
22
+ # :day => 9,
23
+ # :hour => 11,
24
+ # :min => 8,
25
+ # })
26
+ #
27
+ # # typecasting from an object which implements #to_time
28
+ # Post.new(:published_at => DateTime.now)
29
+ #
30
+ class Time < Object
31
+ primitive ::Time
32
+ coercion_method :to_time
33
+
34
+ end # class Time
35
+ end # class Attribute
36
+ end # module Virtus
@@ -0,0 +1,45 @@
1
+ module Virtus
2
+ class Attribute
3
+ class Writer < AccessorMethod
4
+
5
+ class Coercible < self
6
+
7
+ # Return coercer object used by this writer
8
+ #
9
+ # @return [Object]
10
+ #
11
+ # @api private
12
+ attr_reader :coercer
13
+
14
+ # @api private
15
+ def initialize(name, options = {})
16
+ super
17
+ @coercer = options.fetch(:coercer)
18
+ end
19
+
20
+ # @api private
21
+ def call(instance, value)
22
+ super(instance, coerce(value))
23
+ end
24
+
25
+ # Converts the given value to the primitive type
26
+ #
27
+ # @example
28
+ # attribute.coerce(value) # => primitive_value
29
+ #
30
+ # @param [Object] value
31
+ # the value
32
+ #
33
+ # @return [Object]
34
+ # nil, original value or value converted to the primitive type
35
+ #
36
+ # @api public
37
+ def coerce(value)
38
+ coercer.call(value)
39
+ end
40
+
41
+ end # class Coercible
42
+
43
+ end # class Writer
44
+ end # class Attribute
45
+ end # module Virtus
@@ -0,0 +1,73 @@
1
+ motion_require 'accessor_method'
2
+
3
+ module Virtus
4
+ class Attribute
5
+
6
+ # Writer method object
7
+ #
8
+ # @api public
9
+ class Writer < AccessorMethod
10
+
11
+ # Return primitive
12
+ #
13
+ # @return [Class]
14
+ #
15
+ # @api private
16
+ attr_reader :primitive
17
+
18
+ # Return default value instance
19
+ #
20
+ # @return [DefaultValue]
21
+ #
22
+ # @api private
23
+ attr_reader :default_value
24
+
25
+ # Initialize a writer instance
26
+ #
27
+ # @param [#to_sym] name
28
+ #
29
+ # @param [Hash] options
30
+ #
31
+ # @return [undefined]
32
+ #
33
+ # @api private
34
+ def initialize(name, options = {})
35
+ super
36
+ @name = "#{name}=".to_sym
37
+ @primitive = options.fetch(:primitive, ::Object)
38
+ @default_value = DefaultValue.build(options[:default])
39
+ end
40
+
41
+ # Sets instance variable of the attribute
42
+ #
43
+ # @example
44
+ # attribute.set!(instance, value) # => value
45
+ #
46
+ # @return [self]
47
+ #
48
+ # @api public
49
+ def call(instance, value)
50
+ instance.instance_variable_set(instance_variable_name, value)
51
+ end
52
+
53
+ # @api private
54
+ def set_default_value(instance, attribute)
55
+ call(instance, default_value.call(instance, attribute))
56
+ end
57
+
58
+ # Creates an attribute writer method
59
+ #
60
+ # @param [Module] mod
61
+ #
62
+ # @return [self]
63
+ #
64
+ # @api private
65
+ def define_method(accessor, mod)
66
+ mod.define_writer_method(accessor, name, visibility)
67
+ self
68
+ end
69
+
70
+ end # class Writer
71
+
72
+ end # class Attribute
73
+ end # module Virtus