attributed_object 0.2.1

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: 56c23aeb644434fb80ca843707575c2ca7d0ca43
4
+ data.tar.gz: 2d53c23bed6cd5ad8672dafd1259540b06005f30
5
+ SHA512:
6
+ metadata.gz: 75886da44a0f01840c505f8b0802efbc26f22cf3dd6bef8357dd8001afb9b241d53640652c9d88a99737c9fe7e3f2b0cb590c080fede5cfa7ce676009ce9e8d8
7
+ data.tar.gz: 95f1d048ab42905178e3536957933c18cfedee6eca3ca458b40b9aa71b4e51b7219cf9a89e4674f3194211bab3692bfdb34383900027c32972eaf64ede5603b1
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
19
+ .ruby-version
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jg.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jaap Groeneveld
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,222 @@
1
+ # AttributedObject
2
+
3
+ AttributedObject gives easy, dependency free attributes to objects making sure the interface is clean.
4
+ It behaves largely like named arguments. Its possible to have disallowed values (usecase: `disallow: nil`), simple, strict typechecking, default values
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'attributed_object'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install attributed_object
19
+
20
+ ## Usage
21
+
22
+ ### Basic Usage and Errors
23
+ ```ruby
24
+ require 'attributed_object'
25
+
26
+ class MyAttributedObject
27
+ include AttributedObject::Strict
28
+
29
+ attribute :first
30
+ attribute :second, disallow: nil
31
+ attribute :third, default: "my default"
32
+ attribute :forth, default: ->{ Time.now }
33
+ end
34
+
35
+ # This works
36
+ MyAttributedObject.new(
37
+ first: 'value',
38
+ second: 'value',
39
+ third: 'value',
40
+ forth: 'value'
41
+ )
42
+
43
+ # Throws DisallowedValueError
44
+ MyAttributedObject.new(
45
+ first: 'value',
46
+ second: nil,
47
+ third: 'value',
48
+ forth: 'value'
49
+ )
50
+
51
+ # Throws MissingAttributeError
52
+ MyAttributedObject.new(
53
+ second: 'value',
54
+ third: 'value',
55
+ forth: 'value'
56
+ )
57
+
58
+ # Throws UnknownAttributeError
59
+ MyAttributedObject.new(
60
+ something_unknown: 'value',
61
+ first: 'value',
62
+ second: 'value',
63
+ third: 'value',
64
+ forth: 'value'
65
+ )
66
+ ```
67
+
68
+ ### Equality
69
+ ```ruby
70
+ SimpleFoo.new(bar: 12) == SimpleFoo.new(bar: 12)
71
+ ```
72
+
73
+ ### Strict Type Checking
74
+ ```ruby
75
+ class MyTypedAttributedObject
76
+ include AttributedObject::Strict
77
+
78
+ attribute :first, :string, disallow: nil
79
+ attribute :second, MyAttributedObject, default: nil
80
+ end
81
+
82
+ # Works
83
+
84
+ MyTypedAttributedObject.new(
85
+ first: 'hello world',
86
+ second: MyAttributedObject.new(
87
+ first: 'value',
88
+ second: 'value',
89
+ third: 'value',
90
+ forth: 'value'
91
+ )
92
+ )
93
+
94
+ # Throws TypeError
95
+
96
+ MyTypedAttributedObject.new(
97
+ first: 12,
98
+ second: MyAttributedObject.new(
99
+ first: 'value',
100
+ second: 'value',
101
+ third: 'value',
102
+ forth: 'value'
103
+ )
104
+ )
105
+
106
+ # Supported Types:
107
+ # :string
108
+ # :boolean
109
+ # :integer
110
+ # :float
111
+ # :numeric
112
+ # :symbol
113
+ # :array
114
+ # :hash
115
+ # ArrayOf(:integer)
116
+ # HashOf(:symbol, :string)
117
+ # Instances of AttributedObject::Type (example: lib/attributed_object/types/array_of.rb)
118
+ # any Class
119
+ ```
120
+
121
+ ## Coercion
122
+ Instead of raising error when the wrong type is passed, AttributedObject can be configured to use a simple coercion mechanim.
123
+ An example use case is the boundary to web forms.
124
+
125
+ It is also possible to coerce into AttributedObject Structures.
126
+
127
+ For custom coercion see AttributedObject::Type (example: lib/attributed_object/types/array_of.rb)
128
+
129
+ ```ruby
130
+ class Coercable
131
+ include AttributedObject::Coerce
132
+
133
+ attribute :foo, :integer
134
+ end
135
+ Coercable.new(foo: '1').foo # => 1
136
+ ```
137
+
138
+ Example Form Object
139
+ ```ruby
140
+ class FormObject
141
+ include ActiveModel::Model # for validations
142
+ include AttributedObject::Coerce # for attributes
143
+ attributed_object(
144
+ default_to: AttributedObject::TypeDefaults.new, # or default_to: nil if you want to more AR like behavior
145
+ coerce_blanks_to_nil: true # this might be interesting if you dont want to have fields set with values when nothing was entered
146
+ )
147
+ end
148
+ ```
149
+
150
+ ## Extra Options
151
+
152
+ ```ruby
153
+ # defaults:
154
+ {
155
+ default_to: AttributedObject::Unset, # AttributedObject::Unset | any value | AttributedObject::TypeDefaults
156
+ ignore_extra_keys: false, # false | true
157
+ coerce_blanks_to_nil: false, # false | true
158
+ }
159
+ ```
160
+
161
+ ### default_to: nil
162
+ If you want to be able to initialze an AttributedObject without any params, you can change the general default for all fields.
163
+ This also can be set to an instance of AttributedObject::TypeDefaults
164
+ ```ruby
165
+ class Defaulting
166
+ include AttributedObject::Strict
167
+ attributed_object default_to: nil
168
+
169
+ attribute :foo
170
+ end
171
+ Defaulting.new.foo # => nil
172
+
173
+ class TypeDefaulting
174
+ include AttributedObject::Strict
175
+ attributed_object default_to: AttributedObject::TypeDefaults.new
176
+
177
+ attribute :a_string, :string
178
+ attribute :a_integer, :integer
179
+ attrobite :a_class, SimpleFoo
180
+ end
181
+ TypeDefaulting.new.a_string # => ''
182
+ TypeDefaulting.new.a_integer # => 0
183
+ TypeDefaulting.new.a_class # => nil
184
+
185
+ class TypeDefaultingOverwrites
186
+ include AttributedObject::Strict
187
+ attributed_object default_to: AttributedObject::TypeDefaults.new(
188
+ :string => 'my_default_string',
189
+ SimpleFoo => SimpleFoo.new(bar: 'kekse')
190
+ )
191
+
192
+ attribute :a_string, :string
193
+ attribute :a_integer, :integer
194
+ attribute :foo, SimpleFoo
195
+ end
196
+
197
+ TypeDefaultingOverwrites.new.a_string # => 'my_default_string'
198
+ TypeDefaultingOverwrites.new.a_integer # => 0
199
+ TypeDefaultingOverwrites.new.foo # => SimpleFoo.new(bar: 'kekse')
200
+ ```
201
+
202
+ ### ignore_extra_keys: true
203
+ ```ruby
204
+ class WithExtraOptions
205
+ include AttributedObject::Strict
206
+ attributed_object ignore_extra_keys: true
207
+
208
+ attribute :foo
209
+ end
210
+ WithExtraOptions.new(foo: 'asd', something: 'bar') # this will not throw an error, usually it would
211
+ ```
212
+
213
+ ### coerce_blanks_to_nil: true
214
+ This will cause blank strings to equal nil after coercion for all non-string types.
215
+ For example an integer will not become '' => 0, but instead '' => nil.
216
+
217
+ ## Benchmark
218
+
219
+ Of course the other gems can do quite a bit more, but this is interesting anyway.
220
+ (see benchmark_attributed_object.rb)
221
+ Result: Attributed Object is quite a bit fast (2-3x) than other gems for the specific use cases.
222
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'attributed_object/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "attributed_object"
8
+ spec.version = AttributedObject::VERSION
9
+ spec.authors = ["Jaap Groeneveld"]
10
+ spec.email = ["jgroeneveld@me.com"]
11
+ spec.summary = %q{Simple and fast module for named arguments in model initializers}
12
+ spec.description = %q{Simple and fast module for named arguments in model initializers}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ end
@@ -0,0 +1,108 @@
1
+ require 'benchmark'
2
+ # require 'virtus'
3
+ # require 'dry/types'
4
+ require 'attributed_object'
5
+
6
+ # class VirtusValue
7
+ # include Virtus.value_object
8
+ #
9
+ # values do
10
+ # attribute :id, Integer
11
+ # attribute :claimed, Boolean
12
+ # attribute :pro_coach, Boolean
13
+ # attribute :url, String
14
+ # end
15
+ # end
16
+ #
17
+ # class VirtusModel
18
+ # include Virtus.model(:strict => true)
19
+ #
20
+ # attribute :id, Integer
21
+ # attribute :claimed, Boolean
22
+ # attribute :pro_coach, Boolean
23
+ # attribute :url, String
24
+ # end
25
+ #
26
+ class AttrObj
27
+ include AttributedObject::Strict
28
+
29
+ attribute :id, :integer, disallow: nil
30
+ attribute :claimed, :boolean, disallow: nil
31
+ attribute :pro_coach, :boolean, disallow: nil
32
+ attribute :url, :string, disallow: nil
33
+ end
34
+
35
+ class AttrObjCoerce
36
+ include AttributedObject::Coerce
37
+
38
+ attribute :id, :integer, disallow: nil
39
+ attribute :claimed, :boolean, disallow: nil
40
+ attribute :pro_coach, :boolean, disallow: nil
41
+ attribute :url, :string, disallow: nil
42
+ end
43
+ #
44
+ # class DryValue < Dry::Types::Value
45
+ # attribute :id, 'strict.int'
46
+ # attribute :claimed, "strict.bool"
47
+ # attribute :pro_coach, "strict.bool"
48
+ # attribute :url, "strict.string"
49
+ # end
50
+
51
+ class Poro
52
+ attr_reader :id
53
+ attr_reader :claimed
54
+ attr_reader :pro_coach
55
+ attr_reader :url
56
+
57
+ def initialize(id:, claimed:, pro_coach:, url:)
58
+ @id = id
59
+ @claimed = claimed
60
+ @pro_coach = pro_coach
61
+ @url = url
62
+ end
63
+ end
64
+
65
+ iterations = 10_000
66
+ Benchmark.bm(27) do |bm|
67
+ # bm.report('Virtus Value') do
68
+ # iterations.times do
69
+ # VirtusValue.new(id: 1, claimed: true, pro_coach: true, url: 'http://google.de')
70
+ # end
71
+ # end
72
+ #
73
+ # bm.report('DryValue') do
74
+ # iterations.times do
75
+ # DryValue.new(id: 1, claimed: true, pro_coach: true, url: 'http://google.de')
76
+ # end
77
+ # end
78
+ #
79
+ # bm.report('Virtus Model') do
80
+ # iterations.times do
81
+ # VirtusModel.new(id: 1, claimed: true, pro_coach: true, url: 'http://google.de')
82
+ # end
83
+ # end
84
+
85
+ bm.report('AttributedObject') do
86
+ iterations.times do
87
+ AttrObj.new(id: 1, claimed: true, pro_coach: true, url: 'http://google.de')
88
+ end
89
+ end
90
+
91
+ bm.report('AttributedObjectCoerce Match') do
92
+ iterations.times do
93
+ AttrObjCoerce.new(id: 1, claimed: true, pro_coach: true, url: 'http://google.de')
94
+ end
95
+ end
96
+
97
+ bm.report('AttributedObjectCoerce All Strings') do
98
+ iterations.times do
99
+ AttrObjCoerce.new(id: '1', claimed: '1', pro_coach: 'true', url: 'http://google.de')
100
+ end
101
+ end
102
+
103
+ bm.report('Poro') do
104
+ iterations.times do
105
+ Poro.new(id: 1, claimed: true, pro_coach: true, url: 'http://google.de')
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,67 @@
1
+ require 'attributed_object/version'
2
+ require 'attributed_object/base'
3
+ require 'attributed_object/strict'
4
+ require 'attributed_object/coerce'
5
+ require 'attributed_object/type'
6
+ require 'attributed_object/types/array_of'
7
+ require 'attributed_object/types/hash_of'
8
+ require 'attributed_object_helpers/hash_util'
9
+ require 'attributed_object_helpers/type_check'
10
+ require 'attributed_object_helpers/type_coerce'
11
+
12
+ # A module to allow classes to have named attributes as initializing parameters
13
+ # Attributes are required to be explicitely given
14
+ # See Readme for all options
15
+
16
+ module AttributedObject
17
+ # Unset makes the difference between nil and 'not given' possible
18
+ class Unset
19
+ end
20
+
21
+ # TypeDefaults is a option for default_to: - it will set defaults on the given type (integer: 0, boolean: false, string: '' etc)
22
+ class TypeDefaults
23
+ def initialize(args={})
24
+ @args = {
25
+ string: '',
26
+ boolean: false,
27
+ integer: 0,
28
+ float: 0.0,
29
+ numeric: 0,
30
+ symbol: nil,
31
+ array: [],
32
+ hash: {}
33
+ }.merge(args)
34
+ end
35
+
36
+ def fetch(type_info)
37
+ @args.fetch(type_info, nil)
38
+ end
39
+ end
40
+
41
+ class Error < StandardError
42
+ end
43
+
44
+ class KeyError < Error
45
+ def initialize(klass, key, args)
46
+ @klass, @key, @args = klass, key, args
47
+ end
48
+
49
+ def to_s
50
+ "#{self.class}: '#{@key}' for #{@klass} - args given: #{@args}"
51
+ end
52
+ end
53
+
54
+ class MissingAttributeError < KeyError
55
+ end
56
+ class UnknownAttributeError < KeyError
57
+ end
58
+ class DisallowedValueError < KeyError
59
+ end
60
+ class UncoercibleValueError < Error
61
+ end
62
+ class TypeError < KeyError
63
+ end
64
+ class ConfigurationError < Error
65
+ end
66
+ end
67
+