shallow_attributes 0.9.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: 8d9c84f7e0cad4b197b19de9247b98e29355907f
4
+ data.tar.gz: be6015df6fed9d6efb0d65abc9f2a6a2a4bcadd2
5
+ SHA512:
6
+ metadata.gz: 1eca913e91af6856f031b0e677d115f315d079de53d263601e6b7a51df4f186799813d26f30f3d21ae4536faa34e571debc767ed2db76d911cf30f97610140fb
7
+ data.tar.gz: 9f430c8bbe5c73d3a2ad22b0f6cdbe8d0a9b8309e7d5e15291735dea260facc1f7406e532bd53eca02e1c11a698acfb440c4ea5cc472f0fdec5c3ac4b0f6efa5
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ coverage
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ before_install: gem install bundler -v 1.11.2
5
+ rvm:
6
+ - 2.0.0
7
+ - 2.1.8
8
+ - 2.2.4
9
+ - 2.3.0
10
+ - jruby-head
11
+ - rbx-2
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: rbx-2
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at antondavydov.o@gmail.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in shallow_attributes.gemspec
4
+ gemspec
5
+
6
+ if !ENV['TRAVIS']
7
+ gem 'yard', require: false
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Anton Davydov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,305 @@
1
+ # ShallowAttributes
2
+ [![Build Status](https://travis-ci.org/davydovanton/shallow_attributes.svg?branch=master)](https://travis-ci.org/davydovanton/shallow_attributes)
3
+ [![Code Climate](https://codeclimate.com/github/davydovanton/shallow_attributes/badges/gpa.svg)](https://codeclimate.com/github/davydovanton/shallow_attributes)
4
+ [![Inline docs](http://inch-ci.org/github/davydovanton/shallow_attributes.svg?branch=master)](http://inch-ci.org/github/davydovanton/shallow_attributes)
5
+
6
+ Simple and lightweight Virtus analog.
7
+
8
+ ## Motivation
9
+
10
+ There are already a lot of good and flexible gems which solve a similar problem, allowing attributes to be
11
+ defined with their types, for example: virtus, fast_attributes or attrio. However, the disadvantage of these
12
+ gems is performance or API. So, the goal of ShallowAttributes is to provide a simple solution which is similar
13
+ to the Virtus API, simple, fast, understandable and extendable.
14
+
15
+ This is [the performance benchmark](https://gist.github.com/davydovanton/7cf0da532eae71381cbd) of ShallowAttributes compared to virtus gems.
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'shallow_attributes'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ $ bundle
28
+
29
+ Or install it yourself as:
30
+
31
+ $ gem install shallow_attributes
32
+
33
+ ## Examples
34
+
35
+ ### Using ShallowAttributes with Classes
36
+ You can create classes extended with Virtus and define attributes:
37
+
38
+ ```ruby
39
+ class User
40
+ include ShallowAttributes
41
+
42
+ attribute :name, String
43
+ attribute :age, Integer
44
+ attribute :birthday, DateTime
45
+ end
46
+
47
+ class SuperUser < User
48
+ include ShallowAttributes
49
+
50
+ attribute :name, String
51
+ attribute :age, Integer, allow_nil: true
52
+ attribute :birthday, DateTime
53
+ end
54
+
55
+ user = User.new(name: 'Anton', age: 31)
56
+ user.name # => "Anton"
57
+
58
+ user.age = '31' # => 31
59
+ user.age = nil # => nil
60
+ user.age.class # => Fixnum
61
+
62
+ user.birthday = 'November 18th, 1983' # => #<DateTime: 1983-11-18T00:00:00+00:00 (4891313/2,0/1,2299161)>
63
+
64
+ user.attributes # => { name: "Anton", age: 31, birthday: nil }
65
+
66
+ # mass-assignment
67
+ user.attributes = { name: 'Jane', age: 21 }
68
+ user.name # => "Jane"
69
+ user.age # => 21
70
+
71
+ super_user = SuperUser.new
72
+ user.age = nil # => 0
73
+ ```
74
+
75
+ ### Default Values
76
+
77
+ ``` ruby
78
+ class Page
79
+ include ShallowAttributes
80
+
81
+ attribute :title, String
82
+
83
+ # default from a singleton value (integer in this case)
84
+ attribute :views, Integer, default: 0
85
+
86
+ # default from a singleton value (boolean in this case)
87
+ attribute :published, 'Boolean', default: false
88
+
89
+ # default from a callable object (proc in this case)
90
+ attribute :slug, String, default: lambda { |page, attribute| page.title.downcase.gsub(' ', '-') }
91
+
92
+ # default from a method name as symbol
93
+ attribute :editor_title, String, default: :default_editor_title
94
+
95
+ def default_editor_title
96
+ published ? title : "UNPUBLISHED: #{title}"
97
+ end
98
+ end
99
+
100
+ page = Page.new(title: 'Virtus README')
101
+ page.slug # => 'virtus-readme'
102
+ page.views # => 0
103
+ page.published # => false
104
+ page.editor_title # => "UNPUBLISHED: Virtus README"
105
+
106
+ page.views = 10
107
+ page.views # => 10
108
+ page.reset_attribute(:views) # => 0
109
+ page.views # => 0
110
+ ```
111
+
112
+ ## Embedded Value
113
+
114
+ ``` ruby
115
+ class City
116
+ include ShallowAttributes
117
+
118
+ attribute :name, String
119
+ attribute :size, Integer, default: 9000
120
+ end
121
+
122
+ class Address
123
+ include ShallowAttributes
124
+
125
+ attribute :street, String
126
+ attribute :zipcode, String, default: '111111'
127
+ attribute :city, City
128
+ end
129
+
130
+ class User
131
+ include ShallowAttributes
132
+
133
+ attribute :name, String
134
+ attribute :address, Address
135
+ end
136
+
137
+ user = User.new(address: {
138
+ street: 'Street 1/2',
139
+ zipcode: '12345',
140
+ city: {
141
+ name: 'NYC'
142
+ }
143
+ })
144
+
145
+ user.address.street # => "Street 1/2"
146
+ user.address.city.name # => "NYC"
147
+ ```
148
+
149
+ ### Custom Coercions
150
+
151
+ ``` ruby
152
+ require 'json'
153
+
154
+ class Json
155
+ def coerce(value, options = {})
156
+ value.is_a?(::Hash) ? value : JSON.parse(value)
157
+ end
158
+ end
159
+
160
+ class User
161
+ include ShallowAttributes
162
+
163
+ attribute :info, Json, default: {}
164
+ end
165
+
166
+ user = User.new
167
+ user.info = '{"email":"john@domain.com"}' # => {"email"=>"john@domain.com"}
168
+ user.info.class # => Hash
169
+
170
+ # With a custom attribute encapsulating coercion-specific configuration
171
+ class NoisyString
172
+ def coerce(value, options = {})
173
+ value.to_s.upcase
174
+ end
175
+ end
176
+
177
+ class User
178
+ include ShallowAttributes
179
+
180
+ attribute :scream, NoisyString
181
+ end
182
+
183
+ user = User.new(scream: 'hello world!')
184
+ user.scream # => "HELLO WORLD!"
185
+ ```
186
+
187
+ ### Collection Member Coercions
188
+
189
+ ``` ruby
190
+ # Support "primitive" classes
191
+ class Book
192
+ include ShallowAttributes
193
+
194
+ attribute :page_numbers, Array, of: Integer
195
+ end
196
+
197
+ book = Book.new(:page_numbers => %w[1 2 3])
198
+ book.page_numbers # => [1, 2, 3]
199
+
200
+ # Support EmbeddedValues, too!
201
+ class Address
202
+ include ShallowAttributes
203
+
204
+ attribute :address, String
205
+ attribute :locality, String
206
+ attribute :region, String
207
+ attribute :postal_code, String
208
+ end
209
+
210
+ class PhoneNumber
211
+ include ShallowAttributes
212
+
213
+ attribute :number, String
214
+ end
215
+
216
+ class User
217
+ include ShallowAttributes
218
+
219
+ attribute :phone_numbers, Array, of: PhoneNumber
220
+ attribute :addresses, Array, of: Address
221
+ end
222
+
223
+ user = User.new(
224
+ :phone_numbers => [
225
+ { :number => '212-555-1212' },
226
+ { :number => '919-444-3265' } ],
227
+ :addresses => [
228
+ { :address => '1234 Any St.', :locality => 'Anytown', :region => "DC", :postal_code => "21234" } ])
229
+
230
+ user.phone_numbers # => [#<PhoneNumber:0x007fdb2d3bef88 @number="212-555-1212">, #<PhoneNumber:0x007fdb2d3beb00 @number="919-444-3265">]
231
+ user.addresses # => [#<Address:0x007fdb2d3be448 @address="1234 Any St.", @locality="Anytown", @region="DC", @postal_code="21234">]
232
+
233
+ user.attributes # => {
234
+ # => :phone_numbers => [
235
+ # => { :number => '212-555-1212' },
236
+ # => { :number => '919-444-3265' } ],
237
+ # => :addresses => [
238
+ # => { :address => '1234 Any St.', :locality => 'Anytown', :region => "DC", :postal_code => "21234" } ]
239
+ # => }
240
+ ```
241
+
242
+ ### Overriding setters
243
+
244
+ ``` ruby
245
+ class User
246
+ include ShallowAttributes
247
+
248
+ attribute :name, String
249
+
250
+ alias_method :_name=, :name=
251
+ def name=(new_name)
252
+ custom_name = nil
253
+ if new_name == "Godzilla"
254
+ custom_name = "Can't tell"
255
+ end
256
+
257
+ self._name = custom_name || new_name
258
+ end
259
+ end
260
+
261
+ user = User.new(name: "Frank")
262
+ user.name # => 'Frank'
263
+
264
+ user = User.new(name: "Godzilla")
265
+ user.name # => 'Can't tell'
266
+ ```
267
+
268
+ ### ActiveModel validation
269
+
270
+ ``` ruby
271
+ require 'active_model'
272
+
273
+ class Children
274
+ include ShallowAttributes
275
+ include ActiveModel::Validations
276
+
277
+ attribute :scream, String
278
+ validates :scream, presence: true
279
+ end
280
+
281
+ user = User.new(scream: '')
282
+ user.valid? # => false
283
+ user.scream = 'hello world!'
284
+ user.valid? # => true
285
+ ```
286
+
287
+ ## Ruby version support
288
+
289
+ ShallowAttributes is known to work correctly with the following rubies:
290
+
291
+ * 2.3+
292
+
293
+ In future I want ot support other ruby sersion/platforms.
294
+
295
+ ## Contributing
296
+
297
+ Bug reports and pull requests are welcome on GitHub at https://github.com/davydovanton/shallow_attributes.
298
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected
299
+ to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
300
+
301
+
302
+ ## License
303
+
304
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
305
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "shallow_attributes"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,111 @@
1
+ module ShallowAttributes
2
+ # Abstract class for value classes. Provides some helper methods for
3
+ # working with class methods.
4
+ #
5
+ # @abstract
6
+ #
7
+ # @since 0.1.0
8
+ module ClassMethods
9
+ # Returns hash which contain default values for each attribute
10
+ #
11
+ # @private
12
+ #
13
+ # @return [Hash] hash with default values
14
+ #
15
+ # @since 0.1.0
16
+ def default_values
17
+ if superclass.respond_to?(:default_values)
18
+ @default_values.merge!(superclass.default_values) { |_, v, _| v }
19
+ else
20
+ @default_values
21
+ end
22
+ end
23
+
24
+ # Returns all class attributes.
25
+ #
26
+ #
27
+ # @example Create new User instance
28
+ # class User
29
+ # include ShallowAttributes
30
+ # attribute :name, String
31
+ # attribute :last_name, String
32
+ # attribute :age, Integer
33
+ # end
34
+ #
35
+ # User.attributes # => [:name, :last_name, :age]
36
+ #
37
+ # @return [Hash]
38
+ #
39
+ # @since 0.1.0
40
+ def attributes
41
+ default_values.keys
42
+ end
43
+
44
+ # Define attribute with specific type and default value
45
+ # for current class.
46
+ #
47
+ # @param [String, Symbol] name the attribute name
48
+ # @param [String, Symbol] type the type of attribute
49
+ # @param [hash] options the attribute options
50
+ # @option options [Object] :default default value for attribute
51
+ # @option options [Class] :of class of array elems
52
+ # @option options [boolean] :allow_nil cast `nil` to integer or float
53
+ #
54
+ # @example Create new User instance
55
+ # class User
56
+ # include ShallowAttributes
57
+ # attribute :name, String, default: 'Anton'
58
+ # end
59
+ #
60
+ # User.new # => #<User @attributes={:name=>"Anton"}, @name="Anton">
61
+ # User.new(name: 'ben') # => #<User @attributes={:name=>"Ben"}, @name="Ben">
62
+ #
63
+ # @return [Object]
64
+ #
65
+ # @since 0.1.0
66
+ def attribute(name, type, options = {})
67
+ options[:default] ||= [] if type == Array
68
+
69
+ @default_values ||= {}
70
+ @default_values[name] = options.delete(:default)
71
+
72
+ initialize_setter(name, type, options)
73
+ initialize_getter(name)
74
+ end
75
+
76
+ private
77
+
78
+ # Define setter method for each attribute.
79
+ #
80
+ # @private
81
+ #
82
+ # @param [String, Symbol] name the attribute name
83
+ # @param [String, Symbol] type the type of attribute
84
+ # @param [hash] options the attribute options
85
+ #
86
+ # @return [Object]
87
+ #
88
+ # @since 0.1.0
89
+ def initialize_setter(name, type, options)
90
+ module_eval <<-EOS, __FILE__, __LINE__ + 1
91
+ def #{name}=(value)
92
+ @#{name} = ShallowAttributes::Type.coerce(#{type}, value, #{options})
93
+ @attributes[:#{name}] = @#{name}
94
+ end
95
+ EOS
96
+ end
97
+
98
+ # Define getter method for each attribute.
99
+ #
100
+ # @private
101
+ #
102
+ # @param [String, Symbol] name the attribute name
103
+ #
104
+ # @return [Object]
105
+ #
106
+ # @since 0.1.0
107
+ def initialize_getter(name)
108
+ attr_reader name
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,240 @@
1
+ module ShallowAttributes
2
+ # Abstract class for value classes. Provides some helper methods for
3
+ # working with attributes.
4
+ #
5
+ # @abstract
6
+ #
7
+ # @since 0.1.0
8
+ module InstanceMethods
9
+ # Lambda object for gettring attributes hash for specific
10
+ # value object.
11
+ #
12
+ # @private
13
+ #
14
+ # @since 0.1.0
15
+ TO_H_PROC = -> (value) { value.respond_to?(:to_hash) ? value.to_hash : value }
16
+
17
+ # Initialize instance object with specific attributes
18
+ #
19
+ # @param [Hash] attributes the attributes contained in the class
20
+ #
21
+ # @example Create new User instance
22
+ # class User
23
+ # include ShallowAttributes
24
+ # attribute :name, String
25
+ # end
26
+ #
27
+ # User.new(name: 'Anton') # => #<User @attributes={:name=>"Anton"}, @name="Anton">
28
+ #
29
+ # @return the new instance of value class with specific attributes
30
+ #
31
+ # @since 0.1.0
32
+ def initialize(attrs = {})
33
+ @attributes = attrs.delete_if { |key, _| !default_values.key?(key) }
34
+ define_attributes
35
+ define_default_attributes
36
+ end
37
+
38
+ # Returns hash of object attributes
39
+ #
40
+ # @example Returns all user attributs
41
+ # class User
42
+ # include ShallowAttributes
43
+ # attribute :name, String
44
+ # end
45
+ #
46
+ # user = User.new(name: 'Anton')
47
+ # user.attributes # => { name: "Anton" }
48
+ #
49
+ # @return [Hash]
50
+ #
51
+ # @since 0.1.0
52
+ def attributes
53
+ hash = {}
54
+ @attributes.map do |key, value|
55
+ hash[key] =
56
+ value.is_a?(Array) ? value.map!(&TO_H_PROC) : TO_H_PROC.call(value)
57
+ end
58
+ hash
59
+ end
60
+
61
+ # @since 0.1.0
62
+ alias_method :to_h, :attributes
63
+
64
+ # @since 0.1.0
65
+ alias_method :to_hash, :attributes
66
+
67
+ # Mass-assignment attribut values
68
+ #
69
+ # @param [Hash] attributes the attributes which will be assignment
70
+ #
71
+ # @example Assignment new user name
72
+ # class User
73
+ # include ShallowAttributes
74
+ # attribute :name, String
75
+ # end
76
+ #
77
+ # user = User.new(name: 'Anton')
78
+ # user.attributes = { name: "Ben" }
79
+ # user.attributes # => { name: "Ben" }
80
+ #
81
+ # @return [Hash] attibutes hash
82
+ #
83
+ # @since 0.1.0
84
+ def attributes=(attributes)
85
+ @attributes.merge!(attributes)
86
+ define_attributes
87
+ end
88
+
89
+ # Reser specific attribute to defaul value.
90
+ #
91
+ # @param [Symbol] attribute the attribute which will be resete
92
+ #
93
+ # @example Reset name valus
94
+ # class User
95
+ # include ShallowAttributes
96
+ # attribute :name, String, defauil: 'Ben'
97
+ # end
98
+ #
99
+ # user = User.new(name: 'Anton')
100
+ # user.reset_attribute(:name)
101
+ # user.attributes # => { name: "Ben" }
102
+ #
103
+ # @return the last attribute value
104
+ #
105
+ # @since 0.1.0
106
+ def reset_attribute(attribute)
107
+ instance_variable_set("@#{attribute}", default_value_for(attribute))
108
+ end
109
+
110
+ # Sets new values and returns self. Needs for embedded value.
111
+ #
112
+ # @private
113
+ #
114
+ # @param [Hash] values the new attributes for current object
115
+ # @param [Hash] options
116
+ #
117
+ # @example Use embedded values
118
+ # class User
119
+ # include ShallowAttributes
120
+ # attribute :name, String, defauil: 'Ben'
121
+ # end
122
+ #
123
+ # class Post
124
+ # include ShallowAttributes
125
+ # attribute :author, User
126
+ # end
127
+ #
128
+ # post = Post.new(author: { name: 'Anton'} )
129
+ # post.user.name # => 'Anton'
130
+ #
131
+ # @return the object
132
+ #
133
+ # @since 0.1.0
134
+ def coerce(value, options = {})
135
+ self.attributes = value
136
+ self
137
+ end
138
+
139
+ # Equalate two value objects
140
+ #
141
+ # @param [Object] object the other object
142
+ #
143
+ # @example Equalate two value objects
144
+ # class User
145
+ # include ShallowAttributes
146
+ # attribute :name, String, defauil: 'Ben'
147
+ # end
148
+ #
149
+ # user1 = User.new(name: 'Anton')
150
+ # user2 = User.new(name: 'Anton')
151
+ # user1 == user2 # => true
152
+ #
153
+ # @return [boolean]
154
+ #
155
+ # @since 0.1.0
156
+ def ==(object)
157
+ self.to_h == object.to_h
158
+ end
159
+
160
+ # Inspect instance object
161
+ #
162
+ # @example Equalate two value objects
163
+ # class User
164
+ # include ShallowAttributes
165
+ # attribute :name, String, defauil: 'Ben'
166
+ # end
167
+ #
168
+ # user = User.new(name: 'Anton')
169
+ # user.inspect # => "#<User name=\"Anton\">"
170
+ #
171
+ # @return [String]
172
+ #
173
+ # @since 0.1.0
174
+ def inspect
175
+ "#<#{self.class}#{attributes.map{ |k, v| " #{k}=#{v.inspect}" }.join}>"
176
+ end
177
+
178
+ private
179
+
180
+ # Defene default value for attributes.
181
+ #
182
+ # @private
183
+ #
184
+ # @return the object
185
+ #
186
+ # @since 0.1.0
187
+ def define_default_attributes
188
+ default_values.each do |key, value|
189
+ next unless @attributes[key].nil? && !value.nil?
190
+ send("#{key}=", default_value_for(key))
191
+ end
192
+ end
193
+
194
+ # Defene attributes from `@attributes` instance value.
195
+ #
196
+ # @private
197
+ #
198
+ # @return the object
199
+ #
200
+ # @since 0.1.0
201
+ def define_attributes
202
+ @attributes.each do |key, value|
203
+ send("#{key}=", value)
204
+ end
205
+ end
206
+
207
+ # Retrns default value for specific attribute. Defaul values hash
208
+ # takes from class getter `default_values`.
209
+ #
210
+ # @private
211
+ #
212
+ # @return [nil] if default value not defined
213
+ # @return [Object] if default value defined
214
+ #
215
+ # @since 0.1.0
216
+ def default_value_for(attribute)
217
+ value = default_values[attribute]
218
+
219
+ case value
220
+ when Proc
221
+ value.call(self, attribute)
222
+ when Symbol, String
223
+ self.class.method_defined?(value) ? send(value) : value
224
+ else
225
+ value
226
+ end
227
+ end
228
+
229
+ # Returns hash of default class values
230
+ #
231
+ # @private
232
+ #
233
+ # @return [Hash]
234
+ #
235
+ # @since 0.1.0
236
+ def default_values
237
+ @default_values ||= self.class.default_values
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,35 @@
1
+ module ShallowAttributes
2
+ module Type
3
+ # Abstract class for typecast object to Array type.
4
+ #
5
+ # @abstract
6
+ #
7
+ # @since 0.1.0
8
+ class Array
9
+ # Convert value to Array type
10
+ #
11
+ # @private
12
+ #
13
+ # @param [Array] values
14
+ # @param [Hash] options
15
+ # @option options [String] :of the type of array element class
16
+ #
17
+ # @example Convert integer array to string array
18
+ # ShallowAttributes::Type::Array.new.coerce([1, 2], String)
19
+ # # => ['1', '2']
20
+ #
21
+ # @raise [InvalidValueError] if values is not Array
22
+ #
23
+ # @return [Array]
24
+ #
25
+ # @since 0.1.0
26
+ def coerce(values, options = {})
27
+ unless values.is_a? ::Array
28
+ raise ShallowAttributes::Type::InvalidValueError, %(Invalid value "#{values}" for type "Array")
29
+ end
30
+
31
+ values.map! { |value| ShallowAttributes::Type.coerce(options[:of], value) }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,53 @@
1
+ module ShallowAttributes
2
+ module Type
3
+ # Abstract class for typecast object to Boolean type.
4
+ #
5
+ # @abstract
6
+ #
7
+ # @since 0.1.0
8
+ class Boolean
9
+ # Array of true values
10
+ #
11
+ # @private
12
+ #
13
+ # @since 0.1.0
14
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].freeze
15
+
16
+ # Array of false values
17
+ #
18
+ # @private
19
+ #
20
+ # @since 0.1.0
21
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF', nil].freeze
22
+
23
+ # Convert value to Boolean type
24
+ #
25
+ # @private
26
+ #
27
+ # @param [Object] value
28
+ # @param [Hash] option
29
+ #
30
+ # @example Convert integer to boolean value
31
+ # ShallowAttributes::Type::Boolean.new.coerce(1)
32
+ # # => true
33
+ #
34
+ # ShallowAttributes::Type::Boolean.new.coerce(0)
35
+ # # => false
36
+ #
37
+ # @raise [InvalidValueError] if values is not included in true and false arrays
38
+ #
39
+ # @return [boolean]
40
+ #
41
+ # @since 0.1.0
42
+ def coerce(value, options = {})
43
+ if TRUE_VALUES.include?(value)
44
+ true
45
+ elsif FALSE_VALUES.include?(value)
46
+ false
47
+ else
48
+ raise ShallowAttributes::Type::InvalidValueError, %(Invalid value "#{value}" for type "Boolean")
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,37 @@
1
+ module ShallowAttributes
2
+ module Type
3
+ # Abstract class for typecast object to DateTime type.
4
+ #
5
+ # @abstract
6
+ #
7
+ # @since 0.1.0
8
+ class DateTime
9
+ # Convert value to DateTime type
10
+ #
11
+ # @private
12
+ #
13
+ # @param [Object] value
14
+ # @param [Hash] option
15
+ #
16
+ # @example Convert integer to datetime value
17
+ # ShallowAttributes::Type::DateTime.new.coerce('Thu Nov 29 14:33:20 GMT 2001')
18
+ # # => '2001-11-29T14:33:20+00:00'
19
+ #
20
+ # @raise [InvalidValueError] if values is not a sting
21
+ #
22
+ # @return [DateTime]
23
+ #
24
+ # @since 0.1.0
25
+ def coerce(value, options = {})
26
+ case value
27
+ when ::DateTime then value
28
+ when ::Time then ::DateTime.parse(value.to_s)
29
+ else
30
+ ::DateTime.parse(value)
31
+ end
32
+ rescue
33
+ raise ShallowAttributes::Type::InvalidValueError, %(Invalid value "#{value}" for type "DateTime")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ module ShallowAttributes
2
+ module Type
3
+ # Abstract class for typecast object to Float type.
4
+ #
5
+ # @abstract
6
+ #
7
+ # @since 0.1.0
8
+ class Float
9
+ # Convert value to Float type
10
+ #
11
+ # @private
12
+ #
13
+ # @param [Object] value
14
+ # @param [Hash] option
15
+ # @option options [boolean] :allow_nil cast `nil` to integer or float
16
+ #
17
+ # @example Convert string to float value
18
+ # ShallowAttributes::Type::Float.new.coerce('2001')
19
+ # # => 2001.0
20
+ #
21
+ # @raise [InvalidValueError] if values is invalid
22
+ #
23
+ # @return [Float]
24
+ #
25
+ # @since 0.1.0
26
+ def coerce(value, options = {})
27
+ case value
28
+ when nil then options[:allow_nil] ? 0.0 : nil
29
+ when ::TrueClass then 1.0
30
+ when ::FalseClass then 0.0
31
+ else
32
+ value.respond_to?(:to_f) ? value.to_f
33
+ : raise(ShallowAttributes::Type::InvalidValueError, %(Invalid value "#{value}" for type "Float"))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ module ShallowAttributes
2
+ module Type
3
+ # Abstract class for typecast object to Integer type.
4
+ #
5
+ # @abstract
6
+ #
7
+ # @since 0.1.0
8
+ class Integer
9
+ # Convert value to Integer type
10
+ #
11
+ # @private
12
+ #
13
+ # @param [Object] value
14
+ # @param [Hash] option
15
+ # @option options [boolean] :allow_nil cast `nil` to integer or float
16
+ #
17
+ # @example Convert sting to integer value
18
+ # ShallowAttributes::Type::Integer.new.coerce('2001')
19
+ # # => 2001
20
+ #
21
+ # @raise [InvalidValueError] if values is invalid
22
+ #
23
+ # @return [Integer]
24
+ #
25
+ # @since 0.1.0
26
+ def coerce(value, options = {})
27
+ case value
28
+ when nil then options[:allow_nil] ? 0 : nil
29
+ when ::TrueClass then 1
30
+ when ::FalseClass then 0
31
+ else
32
+ value.respond_to?(:to_i) ? value.to_i
33
+ : raise(ShallowAttributes::Type::InvalidValueError, %(Invalid value "#{value}" for type "Integer"))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ module ShallowAttributes
2
+ module Type
3
+ # Abstract class for typecast object to String type.
4
+ #
5
+ # @abstract
6
+ #
7
+ # @since 0.1.0
8
+ class String
9
+ # Convert value to String type
10
+ #
11
+ # @private
12
+ #
13
+ # @param [Object] value
14
+ # @param [Hash] option
15
+ #
16
+ # @example Convert intger to string value
17
+ # ShallowAttributes::Type::String.new.coerce(2001)
18
+ # # => '2001'
19
+ #
20
+ # @return [Sting]
21
+ #
22
+ # @since 0.1.0
23
+ def coerce(value, options = {})
24
+ case value
25
+ when ::Array then value.join
26
+ when ::Hash, ::Class then error(value)
27
+ else
28
+ value.respond_to?(:to_s) ? value.to_s : error(value)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def error(value)
35
+ raise ShallowAttributes::Type::InvalidValueError, %(Invalid value "#{value}" for type "String")
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,37 @@
1
+ module ShallowAttributes
2
+ module Type
3
+ # Abstract class for typecast object to Time type.
4
+ #
5
+ # @abstract
6
+ #
7
+ # @since 0.1.0
8
+ class Time
9
+ # Convert value to Time type
10
+ #
11
+ # @private
12
+ #
13
+ # @param [Object] value
14
+ # @param [Hash] option
15
+ #
16
+ # @example Convert string to Time value
17
+ # ShallowAttributes::Type::Time.new.coerce('Thu Nov 29 14:33:20 GMT 2001')
18
+ # # => '2001-11-29 14:33:20 +0000'
19
+ #
20
+ # @raise [InvalidValueError] if values is not a sting or integer
21
+ #
22
+ # @return [Time]
23
+ #
24
+ # @since 0.1.0
25
+ def coerce(value, options = {})
26
+ case value
27
+ when ::Time then value
28
+ when ::Integer then ::Time.at(value)
29
+ else
30
+ ::Time.parse(value.to_s)
31
+ end
32
+ rescue
33
+ raise ShallowAttributes::Type::InvalidValueError, %(Invalid value "#{value}" for type "Time")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,82 @@
1
+ require 'shallow_attributes/type/array'
2
+ require 'shallow_attributes/type/boolean'
3
+ require 'shallow_attributes/type/date_time'
4
+ require 'shallow_attributes/type/float'
5
+ require 'shallow_attributes/type/integer'
6
+ require 'shallow_attributes/type/string'
7
+ require 'shallow_attributes/type/time'
8
+
9
+ module ShallowAttributes
10
+ # Namespace for standart type classes
11
+ #
12
+ # @since 0.1.0
13
+ module Type
14
+ # Error class for ivalid value types
15
+ #
16
+ # @since 0.1.0
17
+ class InvalidValueError < TypeError
18
+ end
19
+
20
+ # Convert value object to specific Type class
21
+ #
22
+ # @private
23
+ #
24
+ # @param [Class] type the type class object
25
+ # @param [Object] value the value that should be submit to the necessary type
26
+ # @param [Hash] options the options to create a message with.
27
+ # @option options [String] :of The type of array
28
+ # @option options [boolean] :allow_nil cast `nil` to integer or float
29
+ #
30
+ # @example Convert integer to sting type
31
+ # ShallowAttributes::Type.coerce(String, 1)
32
+ # # => '1'
33
+ #
34
+ # @example Convert string to custom hash type
35
+ # ShallowAttributes::Type.instance_for(JsonType, '{"a"=>1}')
36
+ # # => { a: 1 }
37
+ #
38
+ # @return the converted value object
39
+ #
40
+ # @since 0.1.0
41
+ def self.coerce(type, value, options = {})
42
+ type_instance(type).coerce(value, options)
43
+ end
44
+
45
+ private
46
+
47
+ # Hash object with cached type objects.
48
+ #
49
+ # @private
50
+ #
51
+ # @since 0.1.0
52
+ DEFAULT_TYPE_OBJECTS = {
53
+ ::Array => ShallowAttributes::Type::Array.new,
54
+ ::DateTime => ShallowAttributes::Type::DateTime.new,
55
+ ::Float => ShallowAttributes::Type::Float.new,
56
+ ::Integer => ShallowAttributes::Type::Integer.new,
57
+ ::String => ShallowAttributes::Type::String.new,
58
+ ::Time => ShallowAttributes::Type::Time.new
59
+ }.freeze
60
+
61
+ # Returns class object for specific Type class
62
+ #
63
+ # @private
64
+ #
65
+ # @param [Class] type the type class object
66
+ #
67
+ # @example Returns Sting type class
68
+ # ShallowAttributes::Type.instance_for(String)
69
+ # # => ShallowAttributes::Type::Sting class
70
+ #
71
+ # @example Returns other type class
72
+ # ShallowAttributes::Type.instance_for(MySpecialStringType)
73
+ # # => MySpecialStringType class
74
+ #
75
+ # @return [Class]
76
+ #
77
+ # @since 0.1.0
78
+ def self.type_instance(klass)
79
+ DEFAULT_TYPE_OBJECTS[klass] || ShallowAttributes::Type.const_get(klass.name).new
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,6 @@
1
+ module ShallowAttributes
2
+ # Defines the full version
3
+ #
4
+ # @since 0.1.0
5
+ VERSION = "0.9.0".freeze
6
+ end
@@ -0,0 +1,31 @@
1
+ require 'shallow_attributes/class_methods'
2
+ require 'shallow_attributes/instance_methods'
3
+ require 'shallow_attributes/type'
4
+ require 'shallow_attributes/version'
5
+
6
+ # Main module
7
+ #
8
+ # @since 0.1.0
9
+ module ShallowAttributes
10
+ include InstanceMethods
11
+
12
+ # Including ShallowAttributes class methods to specific class
13
+ #
14
+ # @private
15
+ #
16
+ # @param [Class] base the class containing class methods
17
+ #
18
+ # @return [Class] class for incluting ShallowAttributes gem
19
+ #
20
+ # @since 0.1.0
21
+ def self.included(base)
22
+ base.extend(ClassMethods)
23
+ end
24
+ end
25
+
26
+ # Boolean class for working with bool values
27
+ #
28
+ # @private
29
+ #
30
+ # @since 0.1.0
31
+ class Boolean; end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'shallow_attributes/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "shallow_attributes"
8
+ spec.version = ShallowAttributes::VERSION
9
+ spec.authors = ["Anton Davydov"]
10
+ spec.email = ["antondavydov.o@gmail.com"]
11
+
12
+ spec.summary = %q{Attributes for Plain Old Ruby Objects}
13
+ spec.description = %q{Attributes for Plain Old Ruby Objects}
14
+ spec.homepage = "https://github.com/davydovanton/shallow_attributes"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.11"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "minitest", "~> 5.0"
25
+ spec.add_development_dependency "simplecov", '~> 0'
26
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shallow_attributes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Anton Davydov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Attributes for Plain Old Ruby Objects
70
+ email:
71
+ - antondavydov.o@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - CODE_OF_CONDUCT.md
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/setup
85
+ - lib/shallow_attributes.rb
86
+ - lib/shallow_attributes/class_methods.rb
87
+ - lib/shallow_attributes/instance_methods.rb
88
+ - lib/shallow_attributes/type.rb
89
+ - lib/shallow_attributes/type/array.rb
90
+ - lib/shallow_attributes/type/boolean.rb
91
+ - lib/shallow_attributes/type/date_time.rb
92
+ - lib/shallow_attributes/type/float.rb
93
+ - lib/shallow_attributes/type/integer.rb
94
+ - lib/shallow_attributes/type/string.rb
95
+ - lib/shallow_attributes/type/time.rb
96
+ - lib/shallow_attributes/version.rb
97
+ - shallow_attributes.gemspec
98
+ homepage: https://github.com/davydovanton/shallow_attributes
99
+ licenses:
100
+ - MIT
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.5.1
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Attributes for Plain Old Ruby Objects
122
+ test_files: []
123
+ has_rdoc: