attribs 1.0.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: 41d8896fb88e2a974780b53d830f1e1a59f89ec6
4
+ data.tar.gz: ab506e5dd05cf412fe6c2b47117251ef871da5ca
5
+ SHA512:
6
+ metadata.gz: 8c6043299fe57754ebeca285fc1ab41100d3e01805a41126672f2d37151b0cbf3290aaf717a520ebb92e518668585ffacb3a4ea3538cd86a683691fa1ba391d9
7
+ data.tar.gz: 9d6e0984bc0e21bbda2b3555857163762f8d64c42a32e4307432aa3af3c71580fdb233550e7a5de4c352b3067c8efcc25199aa13df1cd4d2f669345ea74d4ce6
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -r spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Arne Brasseur
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,44 @@
1
+ # Attribs
2
+
3
+ Easy and flexible Ruby value objects.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'attribs'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ gem install -g
16
+
17
+
18
+ Or install it directly:
19
+
20
+ $ gem install attribs
21
+
22
+ ## Usage
23
+
24
+ ``` ruby
25
+ class Widget
26
+ include Attribs.new(:color, :size, quantity: 1)
27
+ end
28
+
29
+ w = Widget.new(color: 'blue', size: '10')
30
+ w2 = w.with(color: 'red')
31
+ puts w2.pp
32
+ w.to_h
33
+ ```
34
+
35
+ ## Shoutout
36
+
37
+ To [Anima](https://github.com/mbj/anima), which powers most of what
38
+ Attribs offers.
39
+
40
+ ## License
41
+
42
+ © 2014-2015 Arne Brasseur
43
+
44
+ MIT License (see LICENSE.txt)
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rubygems/package_task'
2
+
3
+ spec = Gem::Specification.load(File.expand_path('../attribs.gemspec', __FILE__))
4
+ gem = Gem::PackageTask.new(spec)
5
+ gem.define
6
+
7
+ desc "Push gem to rubygems.org"
8
+ task :push => :gem do
9
+ sh "git tag v#{spec.version}"
10
+ sh "git push --tags"
11
+ sh "gem push pkg/attribs-#{spec.version}.gem"
12
+ end
13
+
14
+ require 'mutant'
15
+ task :default => :mutant
16
+
17
+ desc "run mutant"
18
+ task :mutant do
19
+ pattern = ENV.fetch('PATTERN', 'Attribs*')
20
+ opts = ENV.fetch('MUTANT_OPTS', '').split(' ')
21
+ result = Mutant::CLI.run(%w[-Ilib -rattribs --use rspec --score 100] + opts + [pattern])
22
+ fail unless result == Mutant::CLI::EXIT_SUCCESS
23
+ end
24
+
25
+ require 'rspec/core/rake_task'
26
+
27
+ desc "run rspec"
28
+ RSpec::Core::RakeTask.new(:rspec) do |t, task_args|
29
+ t.rspec_opts = "-Ispec"
30
+ t.pattern = "spec"
31
+ end
data/attribs.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "attribs"
5
+ gem.version = "1.0.0"
6
+ gem.authors = ["Arne Brasseur"]
7
+ gem.email = ["arne@arnebrasseur.net"]
8
+ gem.summary = %q{Perfect Features Toggles}
9
+ gem.description = %q{Multi-strategy feature flags.}
10
+ gem.homepage = "https://github.com/plexus/attribs"
11
+ gem.license = "MIT"
12
+
13
+ gem.require_paths = ["lib"]
14
+ gem.files = `git ls-files -z`.split("\x0")
15
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^spec/})
17
+ gem.extra_rdoc_files = %w[README.md]
18
+
19
+ gem.add_runtime_dependency "anima", "~> 0.2.0"
20
+
21
+ gem.add_development_dependency "rake"
22
+ gem.add_development_dependency "rspec", "~> 3.1"
23
+ gem.add_development_dependency "mutant-rspec"
24
+ end
@@ -0,0 +1,54 @@
1
+ class Attribs
2
+ module InstanceMethods
3
+ def initialize(attributes = {})
4
+ super(self.class.attributes.defaults.merge(attributes))
5
+ end
6
+
7
+ def with(attributes)
8
+ self.class.new(to_h.update(attributes))
9
+ end
10
+
11
+ def append_to(type, *objects)
12
+ with(type => instance_variable_get("@#{type}") + objects)
13
+ end
14
+
15
+ def to_h_compact
16
+ defaults = self.class.attributes.defaults
17
+ to_h.reject do |attr, value|
18
+ value.equal?(defaults[attr])
19
+ end
20
+ end
21
+
22
+ def pp
23
+ indent = ->(str) { str.lines.map {|l| " #{l}"}.join }
24
+ format = ->(val) { val.respond_to?(:pp) ? val.pp : val.inspect }
25
+
26
+ values = to_h_compact
27
+
28
+ fmt_attrs = values.map do |attr, value|
29
+ fmt_val = case value
30
+ when Array
31
+ if value.inspect.length < 50
32
+ "[#{value.map(&format).join(", ")}]"
33
+ else
34
+ "[\n#{indent[value.map(&format).join(",\n")]}\n]"
35
+ end
36
+ else
37
+ format[value]
38
+ end
39
+ "#{attr}: #{fmt_val}"
40
+ end
41
+
42
+ fmt_attrs_str = fmt_attrs.join(", ")
43
+
44
+ if fmt_attrs_str.length > 50
45
+ fmt_attrs_str = fmt_attrs.join(",\n")
46
+ end
47
+
48
+ if fmt_attrs_str =~ /\n/
49
+ fmt_attrs_str = "\n#{indent[fmt_attrs_str]}\n"
50
+ end
51
+ "#{self.class.name}.new(#{fmt_attrs_str})"
52
+ end
53
+ end
54
+ end
data/lib/attribs.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'anima'
2
+
3
+ class Attribs < Module
4
+ attr_reader :defaults, :names
5
+
6
+ def initialize(*attrs)
7
+ @defaults = attrs.last.instance_of?(Hash) ? attrs.pop : {}
8
+ @names = (attrs + @defaults.keys).uniq
9
+ end
10
+
11
+ def add(*attrs)
12
+ defaults = attrs.last.instance_of?(Hash) ? attrs.pop : {}
13
+ self.class.new(*[*(names+attrs), @defaults.merge(defaults)])
14
+ end
15
+
16
+ def remove(*attrs)
17
+ self.class.new(*[*(names-attrs), @defaults.reject {|k| attrs.include?(k) }])
18
+ end
19
+
20
+ def included(descendant)
21
+ descendant.module_exec(self) do |this|
22
+ include InstanceMethods,
23
+ Anima.new(*this.names)
24
+
25
+ define_singleton_method(:attributes) { this }
26
+ end
27
+ end
28
+ end
29
+
30
+ require 'attribs/instance_methods'
@@ -0,0 +1,234 @@
1
+ RSpec.describe Attribs do
2
+ subject { Class.new.instance_exec(args) {|args| include Attribs.new(*args) } }
3
+ let(:args) { [:foo, bar: 3] }
4
+
5
+ let(:attributes_module) do
6
+ subject.included_modules.detect do |mod|
7
+ mod.instance_of?(described_class)
8
+ end
9
+ end
10
+
11
+ describe '#initialize' do
12
+ it 'should store defaults' do
13
+ expect(attributes_module.defaults).to eql(bar: 3)
14
+ end
15
+
16
+ it 'should store attribute names' do
17
+ expect(attributes_module.names).to eql [:foo, :bar]
18
+ end
19
+
20
+ context 'without defaults' do
21
+ let(:args) { [:foo, :bar] }
22
+
23
+ it 'should have no defaults' do
24
+ expect(attributes_module.defaults).to eql({})
25
+ end
26
+ end
27
+
28
+ context 'with a name given both with and without default' do
29
+ let(:args) { [:foo, foo: 3] }
30
+
31
+ it 'should only store the name once' do
32
+ expect(attributes_module.names).to eql([:foo])
33
+ end
34
+ end
35
+ end
36
+
37
+ describe '#included' do
38
+ it 'should have a hash-based constructor' do
39
+ expect(subject.new(foo: 3, bar: 4).bar).to equal 4
40
+ end
41
+
42
+ it 'should have defaults constructor' do
43
+ expect(subject.new(foo: 3).bar).to equal 3
44
+ end
45
+
46
+ it 'should allow updating through with' do
47
+ expect(subject.new(foo: 3).with(foo: 4).to_h).to eql(foo: 4, bar: 3)
48
+ end
49
+
50
+ it 'should add an #append_to method' do
51
+ expect(subject.new(foo: [6]).append_to(:foo, 7, 8).foo).to eql [6, 7, 8]
52
+ end
53
+
54
+ context 'with all defaults' do
55
+ subject { Class.new { include Attribs.new(foo: 5, bar: 3) } }
56
+
57
+ it 'should be able to construct without arguments' do
58
+ expect(subject.new.to_h).to eql(foo: 5, bar: 3)
59
+ end
60
+ end
61
+
62
+ context 'without any defaults' do
63
+ subject { Class.new { include Attribs.new(:foo, :bar) } }
64
+
65
+ it 'should allow setting all attributes' do
66
+ expect(subject.new(foo: 5, bar: 6).bar).to equal 6
67
+ end
68
+
69
+ it 'should expect all attributes' do
70
+ expect { subject.new(foo: 5) }.to raise_exception
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#add' do
76
+ subject { Class.new(super()) { include attributes.add(baz: 7, bar: 4) } }
77
+
78
+ it 'should make the new attributes available' do
79
+ expect(subject.new(foo: 3, baz: 6).baz).to equal 6
80
+ end
81
+
82
+ it 'should make the old attributes available' do
83
+ expect(subject.new(foo: 3, baz: 6).foo).to equal 3
84
+ end
85
+
86
+ it 'should take new default values' do
87
+ expect(subject.new(foo: 3, baz: 6).bar).to equal 4
88
+ end
89
+
90
+ it 'should make sure attribute names are uniq' do
91
+ expect(subject.attributes.names.length).to equal 3
92
+ end
93
+
94
+ context 'without any defaults' do
95
+ subject { Class.new(super()) { include attributes.add(:bax) } }
96
+
97
+ it 'should allow setting all attributes' do
98
+ expect(subject.new(foo: 5, bar: 6, bax: 7).bax).to equal 7
99
+ end
100
+
101
+ it 'should expect all attributes' do
102
+ expect { subject.new(foo: 5, bar: 6) }.to raise_exception
103
+ end
104
+ end
105
+ end
106
+
107
+ describe '#remove' do
108
+ context 'when removing an attribute with a default' do
109
+ subject { Class.new(super()) { include attributes.remove(:bar) } }
110
+
111
+ it 'should still recognize attributes that were kept' do
112
+ expect(subject.new(foo: 2).foo).to equal 2
113
+ end
114
+
115
+ it 'should no longer recognize the old attributes' do
116
+ expect { subject.new(foo: 3, bar: 3).bar }.to raise_error
117
+ end
118
+ end
119
+
120
+ context 'when removing an attribute without a default' do
121
+ subject { Class.new(super()) { include attributes.remove(:foo) } }
122
+
123
+ it 'should still recognize attributes that were kept' do
124
+ expect(subject.new(bar: 2).bar).to equal 2
125
+ end
126
+
127
+ it 'should no longer recognize the old attributes' do
128
+ expect { subject.new(foo: 3).foo }.to raise_error
129
+ end
130
+
131
+ it 'should keep the defaults' do
132
+ expect(subject.new.bar).to equal 3
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ RSpec.describe Attribs::InstanceMethods do
139
+ let(:widget) do
140
+ Class.new do
141
+ include Attribs.new(:color, :size, options: {})
142
+ def self.name ; 'Widget' ; end
143
+ end
144
+ end
145
+
146
+ let(:widget_container) do
147
+ Class.new do
148
+ include Attribs.new(widgets: [])
149
+ def self.name ; 'WidgetContainer' ; end
150
+ end
151
+ end
152
+
153
+ let(:fixed_width) do
154
+ Class.new do
155
+ def initialize(width)
156
+ @width = width
157
+ end
158
+
159
+ def inspect
160
+ "#" * @width
161
+ end
162
+ end
163
+ end
164
+
165
+ describe '#pp' do
166
+ it 'should render correctly' do
167
+ expect(widget_container.new(widgets: [
168
+ widget.new(color: :green, size: 7),
169
+ widget.new(color: :blue, size: 9, options: {foo: :bar})
170
+ ]).pp).to eql "
171
+ WidgetContainer.new(
172
+ widgets: [
173
+ Widget.new(color: :green, size: 7),
174
+ Widget.new(color: :blue, size: 9, options: {:foo=>:bar})
175
+ ]
176
+ )
177
+ ".strip
178
+ end
179
+
180
+ it 'should inline short arrays' do
181
+ expect(widget_container.new(widgets: [
182
+ fixed_width.new(23),
183
+ fixed_width.new(22)
184
+ ]).pp).to eql "WidgetContainer.new(widgets: [#######################, ######################])"
185
+ end
186
+
187
+ it 'should put longer arrays on multiple lines' do
188
+ expect(widget_container.new(widgets: [
189
+ fixed_width.new(23),
190
+ fixed_width.new(23)
191
+ ]).pp).to eql "WidgetContainer.new(\n widgets: [\n #######################,\n #######################\n ]\n)"
192
+ end
193
+
194
+ it 'should puts attributes on multiple lines if total length exceeds 50 chars' do
195
+ expect(widget.new(color: fixed_width.new(18), size: fixed_width.new(18)).pp).to match /\n/
196
+ expect(widget.new(color: fixed_width.new(18), size: fixed_width.new(17)).pp).to_not match /\n/
197
+ end
198
+ end
199
+
200
+ describe '#append_to' do
201
+ it 'should append to a named collection' do
202
+ expect(widget_container.new(widgets: [:bar]).append_to(:widgets, :foo)).to eql widget_container.new(widgets: [:bar, :foo])
203
+ end
204
+ end
205
+
206
+ describe '#initialize' do
207
+ it 'should take hash-based args' do
208
+ expect(widget_container.new(widgets: [:bar])).to eql widget_container.new.with(widgets: [:bar])
209
+ end
210
+
211
+ it 'should use defaults when available' do
212
+ expect(widget.new(color: :blue, size: 3).options).to eql({})
213
+ end
214
+ end
215
+
216
+ describe '#to_h_compact' do
217
+ it 'should not show values that are identical to the defaults' do
218
+ expect(widget.new(color: :red, size: 7).to_h_compact)
219
+ .to eql({color: :red, size: 7})
220
+ end
221
+
222
+ it 'should include values that are equivalent but not identical' do
223
+ expect(widget.new(color: :red, size: 7, options: {}).to_h_compact)
224
+ .to eql({color: :red, size: 7, options: {}})
225
+ end
226
+ end
227
+
228
+ describe '#with' do
229
+ it 'should update specific attributes' do
230
+ expect(widget.new(color: :red, size: 7).with(color: :blue))
231
+ .to eql(widget.new(color: :blue, size: 7))
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,9 @@
1
+ require 'attribs'
2
+
3
+ RSpec.configure do |rspec|
4
+ rspec.disable_monkey_patching!
5
+ rspec.raise_errors_for_deprecations!
6
+ rspec.around(:each) do |example|
7
+ Timeout.timeout(1, &example)
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: attribs
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Arne Brasseur
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: anima
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mutant-rspec
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: Multi-strategy feature flags.
70
+ email:
71
+ - arne@arnebrasseur.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files:
75
+ - README.md
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - attribs.gemspec
84
+ - lib/attribs.rb
85
+ - lib/attribs/instance_methods.rb
86
+ - spec/attribs_spec.rb
87
+ - spec/spec_helper.rb
88
+ homepage: https://github.com/plexus/attribs
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.4.6
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Perfect Features Toggles
112
+ test_files:
113
+ - spec/attribs_spec.rb
114
+ - spec/spec_helper.rb