objectmancy 1.0.0

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.
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: []