shallow_attributes 0.9.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: 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: