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,65 @@
1
+ module Virtus
2
+
3
+ # A Configuration instance
4
+ class Configuration
5
+
6
+ # Access the coerce setting for this instance
7
+ attr_accessor :coerce
8
+
9
+ # Build new configuration instance using the passed block
10
+ #
11
+ # @example
12
+ # Configuration.build do |config|
13
+ # config.coerce = false
14
+ # end
15
+ #
16
+ # @return [Configuration]
17
+ #
18
+ # @api public
19
+ def self.build(&block)
20
+ new.call(&block)
21
+ end
22
+
23
+ # Initialized a configuration instance
24
+ #
25
+ # @return [undefined]
26
+ #
27
+ # @api private
28
+ def initialize
29
+ @coerce = true
30
+ @coercer = Coercible::Coercer.new
31
+ end
32
+
33
+ # Provide access to the attributes and methods via the passed block
34
+ #
35
+ # @example
36
+ # configuration.call do |config|
37
+ # config.coerce = false
38
+ # end
39
+ #
40
+ # @return [self]
41
+ #
42
+ # @api private
43
+ def call(&block)
44
+ block.call(self) if block_given?
45
+ self
46
+ end
47
+
48
+ # Access the coercer for this instance and optional configure a
49
+ # new coercer with the passed block
50
+ #
51
+ # @example
52
+ # configuration.coercer do |config|
53
+ # config.string.boolean_map = { true => '1', false => '0' }
54
+ # end
55
+ #
56
+ # @return [Coercer]
57
+ #
58
+ # @api private
59
+ def coercer(&block)
60
+ @coercer = Coercible::Coercer.new(&block) if block_given?
61
+ @coercer
62
+ end
63
+
64
+ end # class Configuration
65
+ end # module Virtus
@@ -0,0 +1,16 @@
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.determine_type(name) or super
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,101 @@
1
+ module Virtus
2
+
3
+ # Extensions common for both classes and instances
4
+ module Extensions
5
+ WRITER_METHOD_REGEXP = /=\z/.freeze
6
+ INVALID_WRITER_METHODS = %w[ == != === []= attributes= ].to_set.freeze
7
+
8
+ # A hook called when an object is extended with Virtus
9
+ #
10
+ # @param [Object] object
11
+ #
12
+ # @return [undefined]
13
+ #
14
+ # @api private
15
+ def self.extended(object)
16
+ super
17
+ object.instance_eval do
18
+ extend InstanceMethods
19
+ extend attribute_set
20
+ end
21
+ end
22
+ private_class_method :extended
23
+
24
+ # Defines an attribute on an object's class
25
+ #
26
+ # @example
27
+ # class Book
28
+ # include Virtus
29
+ #
30
+ # attribute :title, String
31
+ # attribute :author, String
32
+ # attribute :published_at, DateTime
33
+ # attribute :page_count, Integer
34
+ # attribute :index # defaults to Object
35
+ # end
36
+ #
37
+ # @param [Symbol] name
38
+ # the name of an attribute
39
+ #
40
+ # @param [Class] type
41
+ # the type class of an attribute
42
+ #
43
+ # @param [#to_hash] options
44
+ # the extra options hash
45
+ #
46
+ # @return [self]
47
+ #
48
+ # @see Attribute.build
49
+ #
50
+ # @api public
51
+ def attribute(name, type, options = {})
52
+ attribute = Attribute.build(name, type, merge_options(options))
53
+ virtus_add_attribute(attribute)
54
+ self
55
+ end
56
+
57
+ # The list of writer methods that can be mass-assigned to in #attributes=
58
+ #
59
+ # @return [Set]
60
+ #
61
+ # @api private
62
+ def allowed_writer_methods
63
+ @allowed_writer_methods ||=
64
+ begin
65
+ allowed_writer_methods = allowed_methods.grep(WRITER_METHOD_REGEXP).to_set
66
+ allowed_writer_methods -= INVALID_WRITER_METHODS
67
+ allowed_writer_methods.freeze
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ # Return an attribute set for that instance
74
+ #
75
+ # @return [AttributeSet]
76
+ #
77
+ # @api private
78
+ def attribute_set
79
+ @attribute_set ||= AttributeSet.new
80
+ end
81
+
82
+ # Add an attribute to the attribute set
83
+ #
84
+ # @return [AttributeSet]
85
+ #
86
+ # @api private
87
+ def virtus_add_attribute(attribute)
88
+ attribute_set << attribute
89
+ end
90
+
91
+ # Merge default options
92
+ #
93
+ # @return [Hash]
94
+ #
95
+ # @api private
96
+ def merge_options(options)
97
+ { :coerce => Virtus.coerce }.merge(options)
98
+ end
99
+
100
+ end # module Extensions
101
+ end # module Virtus
@@ -0,0 +1,165 @@
1
+ module Virtus
2
+
3
+ # Instance methods that are added when you include Virtus
4
+ module InstanceMethods
5
+
6
+ # Set attributes during initialization of an object
7
+ #
8
+ # @param [#to_hash] attributes
9
+ # the attributes hash to be set
10
+ #
11
+ # @return [undefined]
12
+ #
13
+ # @api private
14
+ def initialize(attributes = nil)
15
+ attribute_set.set(self, attributes) if attributes
16
+ set_default_attributes
17
+ end
18
+
19
+ # Returns a value of the attribute with the given name
20
+ #
21
+ # @example
22
+ # class User
23
+ # include Virtus
24
+ #
25
+ # attribute :name, String
26
+ # end
27
+ #
28
+ # user = User.new(:name => 'John')
29
+ # user[:name] # => "John"
30
+ #
31
+ # @param [Symbol] name
32
+ # a name of an attribute
33
+ #
34
+ # @return [Object]
35
+ # a value of an attribute
36
+ #
37
+ # @api public
38
+ def [](name)
39
+ public_send(name)
40
+ end
41
+
42
+ # Sets a value of the attribute with the given name
43
+ #
44
+ # @example
45
+ # class User
46
+ # include Virtus
47
+ #
48
+ # attribute :name, String
49
+ # end
50
+ #
51
+ # user = User.new
52
+ # user[:name] = "John" # => "John"
53
+ # user.name # => "John"
54
+ #
55
+ # @param [Symbol] name
56
+ # a name of an attribute
57
+ #
58
+ # @param [Object] value
59
+ # a value to be set
60
+ #
61
+ # @return [Object]
62
+ # the value set on an object
63
+ #
64
+ # @api public
65
+ def []=(name, value)
66
+ public_send("#{name}=", value)
67
+ end
68
+
69
+ # Returns a hash of all publicly accessible attributes
70
+ #
71
+ # @example
72
+ # class User
73
+ # include Virtus
74
+ #
75
+ # attribute :name, String
76
+ # attribute :age, Integer
77
+ # end
78
+ #
79
+ # user = User.new(:name => 'John', :age => 28)
80
+ # user.attributes # => { :name => 'John', :age => 28 }
81
+ #
82
+ # @return [Hash]
83
+ #
84
+ # @api public
85
+ def attributes
86
+ attribute_set.get(self, &:public_reader?)
87
+ end
88
+ alias_method :to_hash, :attributes
89
+
90
+ # Mass-assign attribute values
91
+ #
92
+ # Keys in the +attributes+ param can be symbols or strings.
93
+ # All referenced Attribute writer methods *will* be called.
94
+ # Non-attribute setter methods on the receiver *will* be called.
95
+ #
96
+ # @example
97
+ # class User
98
+ # include Virtus
99
+ #
100
+ # attribute :name, String
101
+ # attribute :age, Integer
102
+ # end
103
+ #
104
+ # user = User.new
105
+ # user.attributes = { :name => 'John', 'age' => 28 }
106
+ #
107
+ # @param [#to_hash] attributes
108
+ # a hash of attribute names and values to set on the receiver
109
+ #
110
+ # @return [Hash]
111
+ #
112
+ # @api public
113
+ def attributes=(attributes)
114
+ attribute_set.set(self, attributes)
115
+ end
116
+
117
+ # Freeze object
118
+ #
119
+ # @return [self]
120
+ #
121
+ # @api public
122
+ #
123
+ # @example
124
+ #
125
+ # class User
126
+ # include Virtus
127
+ #
128
+ # attribute :name, String
129
+ # attribute :age, Integer
130
+ # end
131
+ #
132
+ # user = User.new(:name => 'John', :age => 28)
133
+ # user.frozen? # => false
134
+ # user.freeze
135
+ # user.frozen? # => true
136
+ #
137
+ # @api public
138
+ def freeze
139
+ set_default_attributes
140
+ super
141
+ end
142
+
143
+ # Set default attributes
144
+ #
145
+ # @return [self]
146
+ #
147
+ # @api private
148
+ def set_default_attributes
149
+ attribute_set.set_defaults(self)
150
+ self
151
+ end
152
+
153
+ private
154
+
155
+ # The list of allowed public methods
156
+ #
157
+ # @return [Array<String>]
158
+ #
159
+ # @api private
160
+ def allowed_methods
161
+ public_methods.map(&:to_s)
162
+ end
163
+
164
+ end # module InstanceMethods
165
+ end # module Virtus
@@ -0,0 +1,92 @@
1
+ module Virtus
2
+
3
+ # Class to build a Virtus module with it's own configuration
4
+ #
5
+ # This allows for individual Virtus modules to be included in
6
+ # classes and not impacted by the global Virtus configuration,
7
+ # which is implemented using Virtus::Configuration.
8
+ class ModuleBuilder
9
+
10
+ # Return module
11
+ #
12
+ # @return [Module]
13
+ #
14
+ # @api private
15
+ attr_reader :module
16
+
17
+ # Return configuration
18
+ #
19
+ # @return [Configuration]
20
+ #
21
+ # @api private
22
+ attr_reader :configuration
23
+
24
+ # Builds a new Virtus module
25
+ #
26
+ # The block is passed to Virtus::Configuration
27
+ #
28
+ # @example
29
+ # ModuleBuilder.call do |config|
30
+ # # config settings
31
+ # end
32
+ #
33
+ # @return [Module]
34
+ #
35
+ # @api public
36
+ def self.call(&block)
37
+ config = Configuration.build(&block)
38
+ builder = new(config)
39
+ builder.add_included_hook
40
+ builder.module
41
+ end
42
+
43
+ # Initializes a new ModuleBuilder
44
+ #
45
+ # @param [Configuration] configuration
46
+ #
47
+ # @param [Module] mod
48
+ #
49
+ # @return [undefined]
50
+ #
51
+ # @api private
52
+ def initialize(configuration, mod = Module.new)
53
+ @configuration = configuration
54
+ @module = mod.send(:include, Virtus)
55
+ end
56
+
57
+ # Adds the .included hook to the anonymous module which then defines the
58
+ # .attribute method to override the default.
59
+ #
60
+ # @return [Module]
61
+ #
62
+ # @api private
63
+ def add_included_hook
64
+ attribute_proc = attribute_method(configuration)
65
+
66
+ self.module.define_singleton_method :included do |object|
67
+ super(object)
68
+ object.send :define_singleton_method, :attribute, attribute_proc
69
+ end
70
+ end
71
+
72
+ # Wrapper for the attribute method that is used in .add_included_hook
73
+ # The coercer is passed in the unused key :configured_coercer to allow the
74
+ # property encapsulation by Virtus::Attribute::Coercer, where the
75
+ # coercion method is known.
76
+ #
77
+ # @return [Proc(lambda)]
78
+ #
79
+ # @api private
80
+ def attribute_method(configuration)
81
+ lambda do |name, type, options = {}|
82
+ module_options = {
83
+ :coerce => configuration.coerce,
84
+ :configured_coercer => configuration.coercer
85
+ }
86
+
87
+ super(name, type, module_options.merge(options))
88
+ end
89
+ end
90
+
91
+ end # class ModuleBuilder
92
+ end # module Virtus
@@ -0,0 +1,72 @@
1
+ module Virtus
2
+
3
+ # Virtus module that can define attributes for later inclusion
4
+ #
5
+ module ModuleExtensions
6
+ include ConstMissingExtensions
7
+
8
+ # Define an attribute in the module
9
+ #
10
+ # @see Virtus::Extensions#attribute
11
+ #
12
+ # @return [self]
13
+ #
14
+ # @api private
15
+ def attribute(*args)
16
+ attribute_definitions << args
17
+ self
18
+ end
19
+
20
+ private
21
+
22
+ # Extend an object with Virtus methods and define attributes
23
+ #
24
+ # @param [Object] object
25
+ #
26
+ # @return [undefined]
27
+ #
28
+ # @api private
29
+ def extended(object)
30
+ super
31
+ object.extend(Virtus)
32
+ define_attributes(object)
33
+ object.set_default_attributes
34
+ end
35
+
36
+ # Extend a class with Virtus methods and define attributes
37
+ #
38
+ # @param [Object] object
39
+ #
40
+ # @return [undefined]
41
+ #
42
+ # @api private
43
+ def included(object)
44
+ super
45
+ object.module_eval { include Virtus }
46
+ define_attributes(object)
47
+ end
48
+
49
+ # Return attribute definitions
50
+ #
51
+ # @return [Array<Hash>]
52
+ #
53
+ # @api private
54
+ def attribute_definitions
55
+ @_attribute_definitions ||= []
56
+ end
57
+
58
+ # Define attributes on a class or instance
59
+ #
60
+ # @param [Object,Class] object
61
+ #
62
+ # @return [undefined]
63
+ #
64
+ # @api private
65
+ def define_attributes(object)
66
+ attribute_definitions.each do |attribute_args|
67
+ object.attribute(*attribute_args)
68
+ end
69
+ end
70
+
71
+ end # module ModuleExtensions
72
+ end # module Virtus
@@ -0,0 +1,2 @@
1
+ class Date
2
+ end
@@ -0,0 +1,2 @@
1
+ class DateTime
2
+ end
@@ -0,0 +1,2 @@
1
+ class BigDecimal
2
+ end
@@ -0,0 +1,149 @@
1
+ #
2
+ # = ostruct.rb: OpenStruct implementation
3
+ #
4
+ # Author:: Yukihiro Matsumoto
5
+ # Documentation:: Gavin Sinclair
6
+ #
7
+ # OpenStruct allows the creation of data objects with arbitrary attributes.
8
+ # See OpenStruct for an example.
9
+ #
10
+
11
+ #
12
+ # OpenStruct allows you to create data objects and set arbitrary attributes.
13
+ # For example:
14
+ #
15
+ # require 'ostruct'
16
+ #
17
+ # record = OpenStruct.new
18
+ # record.name = "John Smith"
19
+ # record.age = 70
20
+ # record.pension = 300
21
+ #
22
+ # puts record.name # -> "John Smith"
23
+ # puts record.address # -> nil
24
+ #
25
+ # It is like a hash with a different way to access the data. In fact, it is
26
+ # implemented with a hash, and you can initialize it with one.
27
+ #
28
+ # hash = { "country" => "Australia", :population => 20_000_000 }
29
+ # data = OpenStruct.new(hash)
30
+ #
31
+ # p data # -> <OpenStruct country="Australia" population=20000000>
32
+ #
33
+ class OpenStruct
34
+ #
35
+ # Create a new OpenStruct object. The optional +hash+, if given, will
36
+ # generate attributes and values. For example.
37
+ #
38
+ # require 'ostruct'
39
+ # hash = { "country" => "Australia", :population => 20_000_000 }
40
+ # data = OpenStruct.new(hash)
41
+ #
42
+ # p data # -> <OpenStruct country="Australia" population=20000000>
43
+ #
44
+ # By default, the resulting OpenStruct object will have no attributes.
45
+ #
46
+ def initialize(hash=nil)
47
+ @table = {}
48
+ if hash
49
+ for k,v in hash
50
+ @table[k.to_sym] = v
51
+ new_ostruct_member(k)
52
+ end
53
+ end
54
+ end
55
+
56
+ # Duplicate an OpenStruct object members.
57
+ def initialize_copy(orig)
58
+ super
59
+ @table = @table.dup
60
+ end
61
+
62
+ def marshal_dump
63
+ @table
64
+ end
65
+ def marshal_load(x)
66
+ @table = x
67
+ @table.each_key{|key| new_ostruct_member(key)}
68
+ end
69
+
70
+ def modifiable
71
+ if self.frozen?
72
+ raise TypeError, "can't modify frozen #{self.class}", caller(2)
73
+ end
74
+ @table
75
+ end
76
+ protected :modifiable
77
+
78
+ def new_ostruct_member(name)
79
+ name = name.to_sym
80
+ unless self.respond_to?(name)
81
+ self.__singleton_class.class_eval do
82
+ define_method(name) { @table[name] }
83
+ define_method("#{name}=") { |x| modifiable[name] = x }
84
+ end
85
+ end
86
+ name
87
+ end
88
+
89
+ def method_missing(mid, *args) # :nodoc:
90
+ mname = mid.id2name
91
+ len = args.length
92
+ if mname.chomp!('=')
93
+ if len != 1
94
+ raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
95
+ end
96
+ modifiable[new_ostruct_member(mname)] = args[0]
97
+ elsif len == 0
98
+ @table[mid]
99
+ else
100
+ raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1)
101
+ end
102
+ end
103
+
104
+ #
105
+ # Remove the named field from the object.
106
+ #
107
+ def delete_field(name)
108
+ @table.delete name.to_sym
109
+ end
110
+
111
+ InspectKey = :__inspect_key__ # :nodoc:
112
+
113
+ #
114
+ # Returns a string containing a detailed summary of the keys and values.
115
+ #
116
+ def inspect
117
+ str = "#<#{self.class}"
118
+
119
+ ids = (Thread.current[InspectKey] ||= [])
120
+ if ids.include?(object_id)
121
+ return str << ' ...>'
122
+ end
123
+
124
+ ids << object_id
125
+ begin
126
+ first = true
127
+ for k,v in @table
128
+ str << "," unless first
129
+ first = false
130
+ str << " #{k}=#{v.inspect}"
131
+ end
132
+ return str << '>'
133
+ ensure
134
+ ids.pop
135
+ end
136
+ end
137
+ alias :to_s :inspect
138
+
139
+ attr_reader :table # :nodoc:
140
+ protected :table
141
+
142
+ #
143
+ # Compare this object and +other+ for equality.
144
+ #
145
+ def ==(other)
146
+ return false unless(other.kind_of?(OpenStruct))
147
+ return @table == other.table
148
+ end
149
+ end