attributed_object 0.2.1

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: 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
+