objectmancy 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/objectmancy/attribute_options.rb +11 -1
- data/lib/objectmancy/common_class_methods.rb +69 -0
- data/lib/objectmancy/hashable.rb +83 -0
- data/lib/objectmancy/objectable.rb +41 -84
- data/lib/objectmancy/types.rb +5 -1
- data/lib/objectmancy/version.rb +1 -1
- data/lib/objectmancy.rb +5 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62c3afc8c03ca9e9c0de91c4935d1476aa51eb18
|
4
|
+
data.tar.gz: 2a1834ac88ef320a58f8c15e6e36618a9bb2bcac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 008e5e952f44195e7ea092f7419805887b7e6803f2b97454c7b80bba24b3a0665fcf2724079123c1d5a3dc4fcfed2889068593eaeaccfd0af548fdb893e94e41
|
7
|
+
data.tar.gz: 60c2a0195efd52ad84ca2c507235e6378a1c8588e60ea0f59529db319de588f1304563a98a49fe666eec783bf5411c6428fabf68226826362d4c50750475b997
|
@@ -7,12 +7,22 @@ module Objectmancy
|
|
7
7
|
# @!attribute [r] type
|
8
8
|
# @!attribute [r] objectable
|
9
9
|
# @!attribute [r] multiple
|
10
|
-
|
10
|
+
# @!attribute [r] hashable
|
11
|
+
attr_reader :type, :objectable, :multiple, :hashable
|
11
12
|
|
12
13
|
def initialize(**options)
|
13
14
|
@type = options[:type]
|
14
15
|
@objectable = options[:objectable]
|
15
16
|
@multiple = options[:multiple]
|
17
|
+
@hashable = options[:hashable]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a new copy of of the given object without a value for the multiple
|
21
|
+
# attribute.
|
22
|
+
#
|
23
|
+
# @return [AttributeOptions]
|
24
|
+
def force_singular
|
25
|
+
AttributeOptions.new(type: type, hashable: hashable)
|
16
26
|
end
|
17
27
|
end
|
18
28
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'objectmancy/errors'
|
2
|
+
require 'objectmancy/attribute_options'
|
3
|
+
require 'objectmancy/hashable'
|
4
|
+
|
5
|
+
module Objectmancy
|
6
|
+
# Class methods mixed into anything including one of the mixins for
|
7
|
+
# Objectmancy
|
8
|
+
module CommonClassMethods
|
9
|
+
# @private
|
10
|
+
# Basic setup of the class-level state.
|
11
|
+
def self.extended(base)
|
12
|
+
base.instance_variable_set(:@registered_attributes, {})
|
13
|
+
base.class.send(:attr_reader, :registered_attributes)
|
14
|
+
base.send(:private_class_method, :attribute, :multiples)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Defines an attribute usable by Objectmancy to create your object.
|
18
|
+
# Only attributes defined with this method will be converted to attributes
|
19
|
+
# on the final object.
|
20
|
+
#
|
21
|
+
# @param name [#to_sym] Attribute name
|
22
|
+
# @param opts [Hash] Options to be applied
|
23
|
+
# @option opts [Symbol, Class] :type The type of object to create. Can be
|
24
|
+
# either a Symbol of one of: :datetime; else, a Class
|
25
|
+
# @option opts [Symbol, String] :objectable Method to call on :type. Will
|
26
|
+
# default to calling :new. Ignored if :type is one of the known types.
|
27
|
+
# Requires :type option.
|
28
|
+
# @raise [AttributeAlreadyDefinedError] Attempt to define two attributes
|
29
|
+
# of the same name
|
30
|
+
# @raise [ArgumentError] Pass in :objectable without :type
|
31
|
+
def attribute(name, **opts)
|
32
|
+
symbolized_name = name.to_sym
|
33
|
+
|
34
|
+
if registered_attributes.key? symbolized_name
|
35
|
+
raise AttributeAlreadyDefinedError, name
|
36
|
+
end
|
37
|
+
|
38
|
+
if opts[:objectable] && opts[:type].nil?
|
39
|
+
raise ArgumentError, ':objectable option reuqires :type option'
|
40
|
+
end
|
41
|
+
|
42
|
+
registered_attributes[symbolized_name] = AttributeOptions.new(opts)
|
43
|
+
|
44
|
+
attr_accessor symbolized_name
|
45
|
+
end
|
46
|
+
|
47
|
+
# Allows the definition of Arrays of items to be turns into objects. Bear
|
48
|
+
# in mind that Arrays of basic objects (Strings, numbers, anything else
|
49
|
+
# that doesn't need special initialization) are handled by .attribute.
|
50
|
+
#
|
51
|
+
# @param name [#to_sym] Attribute name
|
52
|
+
# @param opts [Hash] Options to be applied
|
53
|
+
# @option opts [Symbol, Class] :type The type of object to create. Can be
|
54
|
+
# either a Symbol of one of: :datetime; else, a Class
|
55
|
+
# @option opts [Symbol, String] :objectable Method to call on :type. Will
|
56
|
+
# default to calling :new. Ignored if :type is one of the known types.
|
57
|
+
# @raise [AttributeAlreadyDefinedError] Attempt to define two attributes
|
58
|
+
# of the same name
|
59
|
+
# @raise [ArgumentError] Calling .multiples without :type option for
|
60
|
+
# non-Hashable objects.
|
61
|
+
def multiples(name, **opts)
|
62
|
+
if !ancestors.include?(Objectmancy::Hashable) && opts[:type].nil?
|
63
|
+
raise ArgumentError, 'Multiples require the :type option'
|
64
|
+
end
|
65
|
+
|
66
|
+
attribute(name, opts.merge(multiple: true))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'objectmancy/common_class_methods'
|
2
|
+
require 'objectmancy/types'
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
module Objectmancy
|
6
|
+
# Mixin for allowing your object to be converted into a Hash.
|
7
|
+
module Hashable
|
8
|
+
# ClassMethods specific to Hashable functionality
|
9
|
+
# module ClassMethods
|
10
|
+
# # Allows the definition of Arrays of items to be turns into objects. Bear
|
11
|
+
# # in mind that Arrays of basic objects (Strings, numbers, anything else
|
12
|
+
# # that doesn't need special initialization) are handled by .attribute.
|
13
|
+
# #
|
14
|
+
# # @param name [#to_sym] Attribute name
|
15
|
+
# # @param opts [Hash] Options to be applied
|
16
|
+
# # @option opts [Symbol, Class] :type The type of object to create. Can be
|
17
|
+
# # either a Symbol of one of: :datetime; else, a Class
|
18
|
+
# # @option opts [Symbol, String] :objectable Method to call on :type. Will
|
19
|
+
# # default to calling :new. Ignored if :type is one of the known types.
|
20
|
+
# # @raise [AttributeAlreadyDefinedError] Attempt to define two attributes
|
21
|
+
# # of the same name
|
22
|
+
# def multiples(name, **opts)
|
23
|
+
# super(name, { hashable: true }.merge(opts))
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
|
27
|
+
# @private
|
28
|
+
# This is a little shady, but I'm not sure of a better way to do it.
|
29
|
+
# I gladly welcome suggestions.
|
30
|
+
def self.included(base)
|
31
|
+
base.extend(CommonClassMethods)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Turns the object into a Hash according to the rules defined with
|
35
|
+
#
|
36
|
+
# @return [Hash] Hash representing the object
|
37
|
+
def hashify
|
38
|
+
_present_hashable_values.each_with_object({}) do |(attr, options), memo|
|
39
|
+
memo[attr] = _hashify_value(send(attr), options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Provides current hashable values which have a non-empty value
|
46
|
+
#
|
47
|
+
# @return [Hash] Hash of values
|
48
|
+
def _present_hashable_values
|
49
|
+
self.class.registered_attributes.reject do |attr, _|
|
50
|
+
attr_to_check = send(attr)
|
51
|
+
|
52
|
+
attr_to_check.nil? ||
|
53
|
+
(attr_to_check.respond_to?(:empty?) && attr_to_check.empty?)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Generates the value for the produced hash
|
58
|
+
#
|
59
|
+
# @param value [Object] Object to be used as a value for the hash
|
60
|
+
# @param attribute_options [AttributeOptions] Options for the current
|
61
|
+
# attribute
|
62
|
+
# @return [Object] Value for the final hash
|
63
|
+
def _hashify_value(value, attribute_options)
|
64
|
+
if value.respond_to? :hashify
|
65
|
+
value.hashify
|
66
|
+
elsif attribute_options.multiple
|
67
|
+
value.map { |v| _hashify_value(v, attribute_options.force_singular) }
|
68
|
+
elsif attribute_options.hashable || attribute_options.type
|
69
|
+
_convert_hashable_value(value, attribute_options)
|
70
|
+
else
|
71
|
+
value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def _convert_hashable_value(value, attribute_options)
|
76
|
+
hashable_method =
|
77
|
+
attribute_options.hashable ||
|
78
|
+
Types::SPECIAL_TYPES[attribute_options.type][:hashable]
|
79
|
+
|
80
|
+
value.send(hashable_method)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -1,13 +1,9 @@
|
|
1
|
-
require 'objectmancy/
|
1
|
+
require 'objectmancy/common_class_methods'
|
2
2
|
require 'objectmancy/types'
|
3
|
-
require 'objectmancy/attribute_options'
|
4
|
-
|
5
|
-
require 'pry'
|
6
3
|
|
7
4
|
module Objectmancy
|
8
5
|
# Mixin for allowing your objects to take a Hash and turn them into an object.
|
9
|
-
#
|
10
|
-
# is.
|
6
|
+
#
|
11
7
|
# @example Including Objectable
|
12
8
|
# class Kitten
|
13
9
|
# include Objectmancy::Objectable
|
@@ -17,75 +13,10 @@ module Objectmancy
|
|
17
13
|
#
|
18
14
|
# tabby = Kitten.new(name: 'Eddy')
|
19
15
|
# tabby.name # => "Eddy"
|
20
|
-
#
|
21
|
-
#
|
22
16
|
module Objectable
|
23
17
|
# @private
|
24
18
|
def self.included(base)
|
25
|
-
base.extend(
|
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
|
19
|
+
base.extend(CommonClassMethods)
|
89
20
|
end
|
90
21
|
|
91
22
|
# Creates your object. You should use the after_initialize
|
@@ -94,20 +25,28 @@ module Objectmancy
|
|
94
25
|
def initialize(attrs = {})
|
95
26
|
before_initialize
|
96
27
|
|
97
|
-
|
98
|
-
options = self.class.registered_attributes[attr.to_sym]
|
28
|
+
_attributes_update!(attrs)
|
99
29
|
|
100
|
-
|
101
|
-
|
102
|
-
_convert_multiples(value, options.type, options.objectable)
|
103
|
-
else
|
104
|
-
_single_value(value, options)
|
105
|
-
end
|
30
|
+
after_initialize
|
31
|
+
end
|
106
32
|
|
107
|
-
|
108
|
-
|
33
|
+
# Updates the attributes of the object
|
34
|
+
#
|
35
|
+
# @param attrs [Hash] Attributes to update
|
36
|
+
def mass_update(attrs = {})
|
37
|
+
_attributes_update!(attrs)
|
38
|
+
end
|
109
39
|
|
110
|
-
|
40
|
+
# Comparator for two objects
|
41
|
+
#
|
42
|
+
# @param other [Object]to be compared to
|
43
|
+
# @return [TrueClass, FalseClass] Boolean indicating if the two objects
|
44
|
+
# are equal.
|
45
|
+
def ==(other)
|
46
|
+
self.class == other.class &&
|
47
|
+
self.class.registered_attributes.keys.all? do |attr|
|
48
|
+
send(attr) == other.send(attr)
|
49
|
+
end
|
111
50
|
end
|
112
51
|
|
113
52
|
private
|
@@ -120,12 +59,30 @@ module Objectmancy
|
|
120
59
|
|
121
60
|
# Determines which attributes are assignable
|
122
61
|
#
|
123
|
-
# @param attrs [Hash] Provided
|
62
|
+
# @param attrs [Hash] Provided hash of attributes
|
124
63
|
# @return [Hash] Allowed attributes
|
125
64
|
def _assignable_attributes(attrs)
|
126
65
|
attrs.select { |k, _| self.class.registered_attributes.include? k.to_sym }
|
127
66
|
end
|
128
67
|
|
68
|
+
# Updates the values for defiend attributes
|
69
|
+
#
|
70
|
+
# @param attrs [Hash] Provided hash of attributes
|
71
|
+
def _attributes_update!(attrs)
|
72
|
+
_assignable_attributes(attrs).each do |attr, value|
|
73
|
+
options = self.class.registered_attributes[attr.to_sym]
|
74
|
+
|
75
|
+
value =
|
76
|
+
if options.multiple
|
77
|
+
_convert_multiples(value, options.type, options.objectable)
|
78
|
+
else
|
79
|
+
_single_value(value, options)
|
80
|
+
end
|
81
|
+
|
82
|
+
send("#{attr}=", value)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
129
86
|
# Assigns a multiples attribute
|
130
87
|
#
|
131
88
|
# @param multiples [Array] Array of values to be converted
|
data/lib/objectmancy/types.rb
CHANGED
@@ -4,7 +4,11 @@ module Objectmancy
|
|
4
4
|
# Namespace for storing type logic around manipulating different data types.
|
5
5
|
module Types
|
6
6
|
# DateTime special type specification
|
7
|
-
DATETIME = {
|
7
|
+
DATETIME = {
|
8
|
+
klass: ISO8601::DateTime,
|
9
|
+
objectable: :new,
|
10
|
+
hashable: :to_s
|
11
|
+
}.freeze
|
8
12
|
|
9
13
|
# Known types with special behavior defined.
|
10
14
|
SPECIAL_TYPES = {
|
data/lib/objectmancy/version.rb
CHANGED
data/lib/objectmancy.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
require 'objectmancy/objectable'
|
2
|
+
require 'objectmancy/hashable'
|
2
3
|
|
3
4
|
# Namespace for all Objectmancy functionality
|
4
5
|
# Also serves as a mixin to include Objectable
|
5
6
|
module Objectmancy
|
7
|
+
def self.included(base)
|
8
|
+
base.include(Objectable)
|
9
|
+
base.include(Hashable)
|
10
|
+
end
|
6
11
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: objectmancy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Anderson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: iso8601
|
@@ -75,7 +75,9 @@ extra_rdoc_files: []
|
|
75
75
|
files:
|
76
76
|
- lib/objectmancy.rb
|
77
77
|
- lib/objectmancy/attribute_options.rb
|
78
|
+
- lib/objectmancy/common_class_methods.rb
|
78
79
|
- lib/objectmancy/errors.rb
|
80
|
+
- lib/objectmancy/hashable.rb
|
79
81
|
- lib/objectmancy/objectable.rb
|
80
82
|
- lib/objectmancy/types.rb
|
81
83
|
- lib/objectmancy/version.rb
|
@@ -89,9 +91,9 @@ require_paths:
|
|
89
91
|
- lib
|
90
92
|
required_ruby_version: !ruby/object:Gem::Requirement
|
91
93
|
requirements:
|
92
|
-
- - "
|
94
|
+
- - "~>"
|
93
95
|
- !ruby/object:Gem::Version
|
94
|
-
version: '
|
96
|
+
version: '2.1'
|
95
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
98
|
requirements:
|
97
99
|
- - ">="
|