objectmancy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d58d07ca21141f331f6008c56d07660bc1e9187d
4
+ data.tar.gz: a5856401d36e4899530e11964b5f748fce6cc93a
5
+ SHA512:
6
+ metadata.gz: db17c08f46d637f97c1f4730ef9480a99864ebfe2c92432558fc297a4007ad760a4da6041083c2dbdcae16bbee705c574c5b80210b63a13e3737cfd2c9c279ab
7
+ data.tar.gz: 7c9691474e80119c1c0a3cc94e7fdbb28c2e12e2194b40e10c3cc3ee7ffb4afda0e6abb51e4ddbc976884c14e1c124eae2f26f2c993cb35a811e2402f658700f
@@ -0,0 +1,18 @@
1
+ module Objectmancy
2
+ # @private
3
+ # Class to contain the options for attribute definitions. Makes accessing them
4
+ # more Object-Oriented. This could be written with Objectable, but that felt
5
+ # a little too on-the-nose.
6
+ class AttributeOptions
7
+ # @!attribute [r] type
8
+ # @!attribute [r] objectable
9
+ # @!attribute [r] multiple
10
+ attr_reader :type, :objectable, :multiple
11
+
12
+ def initialize(**options)
13
+ @type = options[:type]
14
+ @objectable = options[:objectable]
15
+ @multiple = options[:multiple]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ module Objectmancy
2
+ # Error for defining attributes with the same name
3
+ class AttributeAlreadyDefinedError < StandardError
4
+ attr_reader :attribute
5
+
6
+ # Creates a new AttributeAlreadyDefinedError
7
+ #
8
+ # @param attribute [#to_s] name of the attribute in error
9
+ def initialize(attribute)
10
+ @attribute = attribute
11
+ end
12
+
13
+ # Message for logging
14
+ #
15
+ # @return [String] message containing the attribute name
16
+ # explaining the error.
17
+ def message
18
+ "#{attribute} has already been defined."
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,177 @@
1
+ require 'objectmancy/errors'
2
+ require 'objectmancy/types'
3
+ require 'objectmancy/attribute_options'
4
+
5
+ require 'pry'
6
+
7
+ module Objectmancy
8
+ # Mixin for allowing your objects to take a Hash and turn them into an object.
9
+ # By default, the values will be whatever the value of the Hash at that key
10
+ # is.
11
+ # @example Including Objectable
12
+ # class Kitten
13
+ # include Objectmancy::Objectable
14
+ #
15
+ # attribute :name
16
+ # end
17
+ #
18
+ # tabby = Kitten.new(name: 'Eddy')
19
+ # tabby.name # => "Eddy"
20
+ #
21
+ #
22
+ module Objectable
23
+ # @private
24
+ def self.included(base)
25
+ base.extend(ClassMethods)
26
+ end
27
+
28
+ # Class methods mixed into anything including Objectable. These are where
29
+ # the real power of Objectmancy comes from.
30
+ module ClassMethods
31
+ # @private
32
+ # Basic setup of the class-level state.
33
+ def self.extended(base)
34
+ base.instance_variable_set(:@registered_attributes, {})
35
+ base.class.send(:attr_reader, :registered_attributes)
36
+ base.send(:private_class_method, :attribute, :multiples)
37
+ end
38
+
39
+ # Defines an attribute usable by Objectmancy to create your object.
40
+ # Only attributes defined with this method will be converted to attributes
41
+ # on the final object.
42
+ #
43
+ # @param name [#to_sym] Attribute name
44
+ # @param opts [Hash] Options to be applied
45
+ # @option opts [Symbol, Class] :type The type of object to create. Can be
46
+ # either a Symbol of one of: :datetime; else, a Class
47
+ # @option opts [Symbol, String] :objectable Method to call on :type. Will
48
+ # default to calling :new. Ignored if :type is one of the known types.
49
+ # Requires :type option.
50
+ # @raise [AttributeAlreadyDefinedError] Attempt to define two attributes
51
+ # of the same name
52
+ # @raise [ArgumentError] Pass in :objectable without :type
53
+ def attribute(name, **opts)
54
+ symbolized_name = name.to_sym
55
+
56
+ if registered_attributes.key? symbolized_name
57
+ raise AttributeAlreadyDefinedError, name
58
+ end
59
+
60
+ if opts[:objectable] && opts[:type].nil?
61
+ raise ArgumentError, ':objectable option reuqires :type option'
62
+ end
63
+
64
+ registered_attributes[symbolized_name] = AttributeOptions.new(opts)
65
+
66
+ attr_accessor symbolized_name
67
+ end
68
+
69
+ # Allows the definition of Arrays of items to be turns into objects. Bear
70
+ # in mind that Arrays of basic objects (Strings, numbers, anything else
71
+ # that doesn't need special initialization) are handled by .attribute.
72
+ #
73
+ # @param name [#to_sym] Attribute name
74
+ # @param opts [Hash] Options to be applied
75
+ # @option opts [Symbol, Class] :type The type of object to create. Can be
76
+ # either a Symbol of one of: :datetime; else, a Class
77
+ # @option opts [Symbol, String] :objectable Method to call on :type. Will
78
+ # default to calling :new. Ignored if :type is one of the known types.
79
+ # @raise [AttributeAlreadyDefinedError] Attempt to define two attributes
80
+ # of the same name
81
+ # @raise [ArgumentError] Calling .multiples without :type option
82
+ def multiples(name, **opts)
83
+ if opts[:type].nil?
84
+ raise ArgumentError, 'Multiples require the :type option'
85
+ end
86
+
87
+ attribute(name, opts.merge(multiple: true))
88
+ end
89
+ end
90
+
91
+ # Creates your object. You should use the after_initialize
92
+ #
93
+ # @param attrs [Hash] Hash of attributes to create the object with
94
+ def initialize(attrs = {})
95
+ before_initialize
96
+
97
+ _assignable_attributes(attrs).each do |attr, value|
98
+ options = self.class.registered_attributes[attr.to_sym]
99
+
100
+ value =
101
+ if options.multiple
102
+ _convert_multiples(value, options.type, options.objectable)
103
+ else
104
+ _single_value(value, options)
105
+ end
106
+
107
+ send("#{attr}=", value)
108
+ end
109
+
110
+ after_initialize
111
+ end
112
+
113
+ private
114
+
115
+ # Empty before_initialize callback
116
+ def before_initialize; end
117
+
118
+ # Empty after_initialize callback
119
+ def after_initialize; end
120
+
121
+ # Determines which attributes are assignable
122
+ #
123
+ # @param attrs [Hash] Provided base hash
124
+ # @return [Hash] Allowed attributes
125
+ def _assignable_attributes(attrs)
126
+ attrs.select { |k, _| self.class.registered_attributes.include? k.to_sym }
127
+ end
128
+
129
+ # Assigns a multiples attribute
130
+ #
131
+ # @param multiples [Array] Array of values to be converted
132
+ # @param type [Symbol, Class] Type of object to create
133
+ # @param creator [#to_s, nil] Method used to create the object
134
+ # @return [Array] Array of newly created objects
135
+ def _convert_multiples(multiples, type, creator)
136
+ creation_klass, creation_method = _creation_method(type, creator)
137
+
138
+ multiples.map { |m| creation_klass.send(creation_method, m) }
139
+ end
140
+
141
+ # Evaluates what needs to be done for a singular value.
142
+ #
143
+ # @param value [Object] Value to be assigned/evaluated.
144
+ # @param options [AttributeOptions] Options pertaining to the attribute
145
+ # @return [Object] Value for the new object
146
+ def _single_value(value, options)
147
+ return value unless options.type
148
+
149
+ _convert_value(value, options.type, options.objectable)
150
+ end
151
+
152
+ # Handles object creation of arbitrary types
153
+ #
154
+ # @param old_value [Object] Value to be converted
155
+ # @param type [Symbol, Class] Type of object to create
156
+ # @param creator [#to_s, nil] Method used to create the object
157
+ # @return [Object] The newly created object
158
+ def _convert_value(old_value, type, creator)
159
+ creation_klass, creation_method = _creation_method(type, creator)
160
+
161
+ creation_klass.send(creation_method, old_value)
162
+ end
163
+
164
+ # Determines the method of creation for a custom object
165
+ #
166
+ # @param type [Symbol, Class] Type of object to create
167
+ # @param creator [#to_s, nil] Method used to create the object
168
+ # @return [Array] Class to create, method to call.
169
+ def _creation_method(type, creator)
170
+ if (known = Types::SPECIAL_TYPES[type])
171
+ [known[:klass], known[:objectable]]
172
+ else
173
+ [type, (creator || :new)]
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,14 @@
1
+ require 'iso8601'
2
+
3
+ module Objectmancy
4
+ # Namespace for storing type logic around manipulating different data types.
5
+ module Types
6
+ # DateTime special type specification
7
+ DATETIME = { klass: ISO8601::DateTime, objectable: :new }.freeze
8
+
9
+ # Known types with special behavior defined.
10
+ SPECIAL_TYPES = {
11
+ datetime: DATETIME
12
+ }.freeze
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ module Objectmancy
2
+ # Version of the gem
3
+ VERSION = '1.0.0'.freeze
4
+ end
@@ -0,0 +1,6 @@
1
+ require 'objectmancy/objectable'
2
+
3
+ # Namespace for all Objectmancy functionality
4
+ # Also serves as a mixin to include Objectable
5
+ module Objectmancy
6
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: objectmancy
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jon Anderson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: iso8601
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.9'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: '0.43'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: '0.43'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ description: Set of extensions used to convert a hash to an object.
70
+ email:
71
+ - jon1992@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/objectmancy.rb
77
+ - lib/objectmancy/attribute_options.rb
78
+ - lib/objectmancy/errors.rb
79
+ - lib/objectmancy/objectable.rb
80
+ - lib/objectmancy/types.rb
81
+ - lib/objectmancy/version.rb
82
+ homepage: https://github.com/jon2992/objectmancy
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.5.1
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Set of extensions used to convert a hash to an object.
106
+ test_files: []