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