attributes_dsl 0.0.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: da436a8b36ce35a3c4b4b87b33ed84a03a7dd910
4
+ data.tar.gz: 8d7edd3acf452eff9b4255a15a16cfee6f1d00d1
5
+ SHA512:
6
+ metadata.gz: 63ea9addb5f796e1009f0140cc3523b4be461c280811d19b878edb3247ae5e1abe9f1cda60a05c703dfe7c28558bbf2868e2432c0b5957bce0d6ab15b0a8b408
7
+ data.tar.gz: e2555ba15ad33fbadbded4f29c0936687e23273ca534de57a9af3767a82f0a3372561d39c1145ec0031ca44f6afe4b0168d871173573c1b41b2f27ecc722fe74
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ service_name: travis-ci
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ *.lock
3
+ .bundle/
4
+ .yardoc/
5
+ coverage/
6
+ doc/
7
+ log/
8
+ pkg/
9
+ tmp/
data/.metrics ADDED
@@ -0,0 +1,9 @@
1
+ # Settings for metric_fu and its packages are collected in the `config/metrics`
2
+ # and loaded by the Hexx::Suit::Metrics::MetricFu.
3
+
4
+ begin
5
+ require "hexx-suit"
6
+ Hexx::Suit::Metrics::MetricFu.load
7
+ rescue LoadError
8
+ puts "The 'hexx-suit' gem is not installed"
9
+ end
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ inherit_from: "./config/metrics/rubocop.yml"
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ ---
2
+ language: ruby
3
+ bundler_args: --without metrics --without benchmarks
4
+ cache: bundler
5
+ script: bundle exec rake test:coverage:run
6
+ rvm:
7
+ - '1.9.3'
8
+ - '2.0'
9
+ - '2.1'
10
+ - '2.2'
11
+ - ruby-head
12
+ - rbx-2 --1.9
13
+ - rbx-2 --2.1
14
+ - jruby-1.7-19mode
15
+ - jruby-9.0.0.0
16
+ - jruby-head
17
+ matrix:
18
+ allow_failures:
19
+ - rvm: ruby-head
20
+ - rvm: jruby-head
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --asset LICENSE
2
+ --exclude lib/attributes_dsl/version.rb
3
+ --output doc/api
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## version 0.0.1 2015-09-10
2
+
3
+ This is the first published version
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :metrics do
6
+ gem "hexx-suit", "~> 2.3" if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.1"
7
+ end
8
+
9
+ group :benchmarks do
10
+ gem "virtus"
11
+ gem "benchmark-ips"
12
+ gem "active_attr"
13
+ gem "fast_attributes"
14
+ gem "anima"
15
+ gem "kwattr"
16
+ end
data/Guardfile ADDED
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ guard :rspec, cmd: "bundle exec rspec" do
4
+
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+
7
+ watch(%r{^lib/attributes_dsl/(.+)\.rb}) do |m|
8
+ "spec/unit/#{m[1]}_spec.rb"
9
+ end
10
+
11
+ watch("lib/attributes_dsl.rb") { "spec" }
12
+ watch("spec/spec_helper.rb") { "spec" }
13
+
14
+ end # guard :rspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2015 Andrew Kozin (nepalez), andrew.kozin@gmail.com
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,209 @@
1
+ Attributes DSL
2
+ ==============
3
+
4
+ [![Gem Version](https://img.shields.io/gem/v/attributes_dsl.svg?style=flat)][gem]
5
+ [![Build Status](https://img.shields.io/travis/nepalez/attributes_dsl/master.svg?style=flat)][travis]
6
+ [![Dependency Status](https://img.shields.io/gemnasium/nepalez/attributes_dsl.svg?style=flat)][gemnasium]
7
+ [![Code Climate](https://img.shields.io/codeclimate/github/nepalez/attributes_dsl.svg?style=flat)][codeclimate]
8
+ [![Coverage](https://img.shields.io/coveralls/nepalez/attributes_dsl.svg?style=flat)][coveralls]
9
+ [![Inline docs](http://inch-ci.org/github/nepalez/attributes_dsl.svg)][inch]
10
+
11
+ Lightweight DSL to define PORO attributes.
12
+
13
+ Uses immutable (deeply frozen) instances via [ice_nine][ice_nine] gem.
14
+
15
+ Synopsis
16
+ --------
17
+
18
+ ```ruby
19
+ require "attributes_dsl"
20
+
21
+ class User
22
+ extend AttributesDSL
23
+
24
+ # `name` is required should be symbolized
25
+ attribute :name, required: true do |value|
26
+ value.to_s.to_sym
27
+ end
28
+
29
+ # `sex` is optional and set to `:male` by default.
30
+ # It can be set to either :male or :female
31
+ attribute :sex, default: :male do |value|
32
+ (value == :male )? :male : :female
33
+ end
34
+
35
+ # `age` is optional and set to `nil` by default.
36
+ # Then it is converted to integer
37
+ attribute :age, &:to_i
38
+
39
+ # `position` is optional and set to `nil` by default
40
+ attribute :position
41
+
42
+ # All other attributes are ignored
43
+ end
44
+
45
+ user = User.new(name: "Jane", sex: :women, age: "26", place: "Moscow")
46
+ user.attributes
47
+ # => { name: :Jane, sex: :female, age: 26, position: nil }
48
+
49
+ # Aliases for attributes[:some_attribute]
50
+ user.name # => :Jane
51
+ user.sex # => :female
52
+ user.age # => 26
53
+ user.position # => nil
54
+
55
+ # Required attributes should be assigned:
56
+ user = User.new(sex: :women, age: "26", place: "Moscow")
57
+ # => #<ArgumentError "Undefined attributes: name">
58
+ ```
59
+
60
+ Additional Details
61
+ ------------------
62
+
63
+ The `attribute` class method takes the `name` and 2 options:
64
+ - `:default` for the default value (otherwise `nil`);
65
+ - `:required` to declare the attribute as required. It will be ignored if a default value is provided!
66
+
67
+ It is also takes the block, used to coerce a value. The coercer is applied to the default value too.
68
+
69
+ Also notice, that instance methods (like `#name`) are just aliases for the corresponding value of the `#attributes` hash. Instance variables aren't defined for them (to ensure syncronization between `#name` and `#attributes[:name]`):
70
+
71
+ ```ruby
72
+ user = User.new(name: "John")
73
+ user.attributes # => { name: :John, sex: :male, age: 0, position: nil }
74
+ user.name # => :John
75
+
76
+ # but
77
+ user.instance_variable_get :@name # => nil
78
+ ```
79
+
80
+ You're free to redefine attributes (class settings are used by the initializer only):
81
+
82
+ ```ruby
83
+ user.attributes[:name] = "Jim"
84
+ user.attributes # => { name: "Jim", sex: :male, age: 0, position: nil }
85
+ user.name # => "Jim"
86
+ ```
87
+
88
+ But if you (like me) prefer instance immutability, you can deeply freeze instances safely:
89
+
90
+ ```ruby
91
+ require "ice_nine"
92
+
93
+ class User
94
+ # ... staff like before
95
+
96
+ def initializer(attributes)
97
+ super
98
+ IceNine.deep_freeze(self)
99
+ end
100
+ end
101
+
102
+ args = { user: "Joe" }
103
+
104
+ user = User.new(args)
105
+ user.frozen? # => true
106
+ user.attributes.frozen? # => true
107
+
108
+ # "Safely" means:
109
+ args.frozen? # => false
110
+ ```
111
+
112
+ Freezing instances to exclude side effects is a part of my coding style. That's why the gem doesn't (and won't do) care about changing attributes after initialization.
113
+
114
+ Benchmarks
115
+ ----------
116
+
117
+ The list of gems to compare has been taken from [Idiosyncratic Ruby #18][idiosyncratic_ruby] by Jan Lelis. I've selected only those gems that support initialization from hash.
118
+
119
+ Look at the [benchmark source][benchmark] for details.
120
+
121
+ The results are following:
122
+
123
+ ```
124
+ -------------------------------------------------
125
+ anima 202.863k (± 5.1%) i/s - 1.023M
126
+ kwattr 171.461k (± 5.3%) i/s - 855.680k
127
+ fast_attributes 156.596k (± 2.5%) i/s - 785.792k
128
+ attributes_dsl 58.966k (± 4.6%) i/s - 296.514k
129
+ active_attr 58.322k (± 2.8%) i/s - 293.205k
130
+ virtus 45.734k (± 2.7%) i/s - 228.960k
131
+
132
+ Comparison:
133
+ anima: 202862.9 i/s
134
+ kwattr: 171460.9 i/s - 1.18x slower
135
+ fast_attributes: 156596.3 i/s - 1.30x slower
136
+ attributes_dsl: 58966.5 i/s - 3.44x slower
137
+ active_attr: 58321.6 i/s - 3.48x slower
138
+ virtus: 45734.3 i/s - 4.44x slower
139
+ ```
140
+
141
+ Results above are pretty reasonable.
142
+
143
+ The gem is faster than `virtus` that has many additional features.
144
+
145
+ It is as fast as `active_attrs` (but has more customizable coercers).
146
+
147
+ It is 2 times slower than `fast_attributes` that has no coercer and default values. And it is 3-3.5 times slower than `anima` and `kwattr` that provides only the base settings.
148
+
149
+ Installation
150
+ ------------
151
+
152
+ Add this line to your application's Gemfile:
153
+
154
+ ```ruby
155
+ # Gemfile
156
+ gem "attributes_dsl"
157
+ ```
158
+
159
+ Then execute:
160
+
161
+ ```
162
+ bundle
163
+ ```
164
+
165
+ Or add it manually:
166
+
167
+ ```
168
+ gem install attributes_dsl
169
+ ```
170
+
171
+ Compatibility
172
+ -------------
173
+
174
+ Tested under rubies [compatible to MRI 1.9+][versions].
175
+
176
+ Uses [RSpec][rspec] 3.0+ for testing and [hexx-suit][hexx-suit] for dev/test tools collection.
177
+
178
+ 100% [mutant]-proof covered.
179
+
180
+ Contributing
181
+ ------------
182
+
183
+ * Read the [STYLEGUIDE](config/metrics/STYLEGUIDE)
184
+ * [Fork the project](https://github.com/nepalez/attributes_dsl)
185
+ * Create your feature branch (`git checkout -b my-new-feature`)
186
+ * Add tests for it
187
+ * Run `rake mutant` or `rake exhort` to ensure 100% [mutant][mutant] coverage
188
+ * Commit your changes (`git commit -am '[UPDATE] Add some feature'`)
189
+ * Push to the branch (`git push origin my-new-feature`)
190
+ * Create a new Pull Request
191
+
192
+ License
193
+ -------
194
+
195
+ See the [MIT LICENSE](LICENSE).
196
+
197
+ [codeclimate]: https://codeclimate.com/github/nepalez/attributes_dsl
198
+ [coveralls]: https://coveralls.io/r/nepalez/attributes_dsl
199
+ [gem]: https://rubygems.org/gems/attributes_dsl
200
+ [gemnasium]: https://gemnasium.com/nepalez/attributes_dsl
201
+ [travis]: https://travis-ci.org/nepalez/attributes_dsl
202
+ [inch]: https://inch-ci.org/github/nepalez/attributes_dsl
203
+ [versions]: .travis.yml
204
+ [rspec]: http://rspec.org
205
+ [hexx-suit]: https://github.com/hexx-rb/hexx-suit
206
+ [mutant]: https://github.com/mbj/mutant
207
+ [ice_nine]: https://github.com/dkubb/ice_nine
208
+ [idiosyncratic_ruby]: http://idiosyncratic-ruby.com/18-con-struct-attributes.html
209
+ [benchmark]: benchmark/run.rb
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ require "bundler/setup"
4
+ require "rubygems"
5
+
6
+ # Loads bundler tasks
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ # Loads the Hexx::RSpec and its tasks
10
+ begin
11
+ require "hexx-suit"
12
+ Hexx::Suit.install_tasks
13
+ rescue LoadError
14
+ require "hexx-rspec"
15
+ Hexx::RSpec.install_tasks
16
+ end
17
+
18
+ desc "Sets the Hexx::RSpec :test task to default"
19
+ task :default do
20
+ system "bundle exec rake test:coverage:run"
21
+ end
22
+
23
+ desc "Runs mutation metric for testing"
24
+ task :mutant do
25
+ system "mutant -r ./spec/spec_helper --use rspec AttributesDSL* --fail-fast"
26
+ end
27
+
28
+ desc "Exhort all evils"
29
+ task :exhort do
30
+ system "mutant -r ./spec/spec_helper --use rspec AttributesDSL*"
31
+ end
32
+
33
+ desc "Runs all the necessary metrics before making a commit"
34
+ task prepare: %w(exhort check:inch check:rubocop check:fu)
35
+
36
+ desc "Runs benchmarks"
37
+ task :benchmark do
38
+ system "ruby benchmark/run.rb"
39
+ end
@@ -0,0 +1,26 @@
1
+ $LOAD_PATH.push File.expand_path("../lib", __FILE__)
2
+ require "attributes_dsl/version"
3
+
4
+ Gem::Specification.new do |gem|
5
+
6
+ gem.name = "attributes_dsl"
7
+ gem.version = AttributesDSL::VERSION.dup
8
+ gem.author = "Andrew Kozin"
9
+ gem.email = "andrew.kozin@gmail.com"
10
+ gem.homepage = "https://github.com/nepalez/attributes_dsl"
11
+ gem.summary = "Lightweight DSL to define PORO attributes"
12
+ gem.license = "MIT"
13
+
14
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
15
+ gem.test_files = Dir["spec/**/*.rb"]
16
+ gem.extra_rdoc_files = Dir["README.md", "LICENSE"]
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.required_ruby_version = ">= 1.9.3"
20
+
21
+ gem.add_runtime_dependency "ice_nine", "~> 0.11"
22
+ gem.add_runtime_dependency "equalizer", "~> 0.0", ">= 0.0.11"
23
+
24
+ gem.add_development_dependency "hexx-rspec", "~> 0.5", ">= 0.5.2"
25
+
26
+ end # Gem::Specification
data/benchmark/run.rb ADDED
@@ -0,0 +1,122 @@
1
+ require "benchmark/ips"
2
+
3
+ module AttributesDSLExample
4
+ require "attributes_dsl"
5
+
6
+ class User
7
+ extend AttributesDSL
8
+
9
+ attribute :foo, required: true, &:to_s
10
+ attribute :bar, default: :BAR, &:to_s
11
+ attribute :baz, default: :BAZ, &:to_s
12
+ attribute :qux, &:to_s
13
+ end
14
+
15
+ def self.call
16
+ User.new(foo: :FOO, bar: :BAR, baz: :BAZ, qux: :QUX)
17
+ end
18
+ end
19
+
20
+ module VirtusExample
21
+ require "virtus"
22
+
23
+ class User
24
+ include Virtus.model
25
+
26
+ attribute :foo, String, required: true
27
+ attribute :bar, String, default: :BAR
28
+ attribute :baz, String, default: :BAZ
29
+ attribute :qux, String
30
+ end
31
+
32
+ def self.call
33
+ User.new(foo: :FOO, bar: :BAR, baz: :BAZ, qux: :QUX)
34
+ end
35
+ end
36
+
37
+ module ActiveAttrExample
38
+ require "active_attr"
39
+
40
+ class User
41
+ include ActiveAttr::MassAssignment
42
+ attr_accessor :foo, :bar, :baz, :qux
43
+ end
44
+
45
+ def self.call
46
+ User.new(foo: :FOO, bar: :BAR, baz: :BAZ, qux: :QUX)
47
+ end
48
+ end
49
+
50
+ module FastAttributesExample
51
+ require "fast_attributes"
52
+
53
+ class User
54
+ extend FastAttributes
55
+
56
+ define_attributes initialize: true do
57
+ attribute :foo, String
58
+ attribute :bar, String
59
+ attribute :baz, String
60
+ attribute :qux, String
61
+ end
62
+ end
63
+
64
+ def self.call
65
+ User.new(foo: :FOO, bar: :BAR, baz: :BAZ, qux: :QUX)
66
+ end
67
+ end
68
+
69
+ module AnimaExample
70
+ require "anima"
71
+
72
+ class User
73
+ include Anima.new(:foo, :bar, :baz, :qux)
74
+ end
75
+
76
+ def self.call
77
+ User.new(foo: :FOO, bar: :BAR, baz: :BAZ, qux: :QUX)
78
+ end
79
+ end
80
+
81
+ module KwattrExample
82
+ require "kwattr"
83
+
84
+ class User
85
+ kwattr :foo, :bar, :baz, :qux
86
+ end
87
+
88
+ def self.call
89
+ User.new(foo: :FOO, bar: :BAR, baz: :BAZ, qux: :QUX)
90
+ end
91
+ end
92
+
93
+ # Provide a benchmark
94
+ Benchmark.ips do |x|
95
+ x.config time: 5, warmup: 5
96
+
97
+ x.report("attributes_dsl") do
98
+ AttributesDSLExample.call
99
+ end
100
+
101
+ x.report("virtus") do
102
+ VirtusExample.call
103
+ end
104
+
105
+ x.report("active_attr") do
106
+ AttributesDSLExample.call
107
+ end
108
+
109
+ x.report("fast_attributes") do
110
+ FastAttributesExample.call
111
+ end
112
+
113
+ x.report("anima") do
114
+ AnimaExample.call
115
+ end
116
+
117
+ x.report("kwattr") do
118
+ KwattrExample.call
119
+ end
120
+
121
+ x.compare!
122
+ end