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