attr_extras 5.2.0 → 6.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 31be8de6a1c2dbbb19e44ea4255d099e656728b3
4
- data.tar.gz: f5a8e9dbfdef34e1f1d368c889ae7a0f825e07e2
2
+ SHA256:
3
+ metadata.gz: e4f8e12f94bbc166632e1a65a52f83ddc14e4982ad57f87043fa285e7e7a8cf2
4
+ data.tar.gz: 1d621e46616f5df763909d9481f983d60fe4e014a8a9a3b0dbfc2635d8867d71
5
5
  SHA512:
6
- metadata.gz: 30d562066800bdcc69011c4eb7ed26966d61aae06e4ebd1050ba7346bb15ef1b131edaa547f2aec9504f61ef103c9022126c40a2ebdd12bf4838d3b8722f387d
7
- data.tar.gz: 4ba342974d86d1c4d0d3eeb30d41caa1fa97720b4626e7bbbafe7245fbf5dd48c4a10ef52c7ce8a43d3f2557a9c7ccb19218c2c21fc94e711fe31a7918958f32
6
+ metadata.gz: 44cade5ed299c18a1e8c01706e04767f1b49f3a934a53759542702fd6435c8ba225b360250dd276a78174fdea32e112ca1155cf20e172db765940ea05ecb3b03
7
+ data.tar.gz: 7fe165a6de71503666cb3344478c769385d50a31ca11de78cce7f934d87393867a5e2c3bc05703c7a3fdc86dd81da729703452c35d0999600f70b42a35851629
@@ -1,5 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.1
4
- - 2.0.0
5
- - jruby-19mode
3
+ - 2.7.1
4
+ - 2.6.6
5
+ - 2.5.8
6
+ - jruby-head
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ ## [6.2.4](https://github.com/barsoom/attr_extras/releases/tag/v6.2.4)
4
+
5
+ - Fix keyword argument warnings with Ruby 2.7. Thanks to [Elliot Winkler](https://github.com/barsoom/attr_extras/pull/34)!
6
+
7
+ ## [6.2.3](https://github.com/barsoom/attr_extras/releases/tag/v6.2.3)
8
+
9
+ - `attr_implement` error says "an 'ear()' method" instead of "a 'ear()' method", when the method starts with a likely vowel.
10
+
11
+ ## [6.2.2](https://github.com/barsoom/attr_extras/releases/tag/v6.2.2)
12
+
13
+ - Fix warnings with Ruby 2.7. Thanks to [Juanito Fatas](https://github.com/barsoom/attr_extras/pull/31)!
14
+ - Fix deprecation warnings for Minitest 6. Thanks again to [Juanito Fatas](https://github.com/barsoom/attr_extras/pull/30)!
15
+
16
+ ## [6.2.1](https://github.com/barsoom/attr_extras/releases/tag/v6.2.1)
17
+
18
+ * Bugfix with keyword argument defaults. Thanks to [Roman Dubrovsky](https://github.com/barsoom/attr_extras/pull/29)!
19
+
20
+ ## [6.2.0](https://github.com/barsoom/attr_extras/releases/tag/v6.2.0)
21
+
22
+ * Another bugfix when passing hash values to positional arguments.
23
+
24
+ ## [6.1.0](https://github.com/barsoom/attr_extras/releases/tag/v6.1.0)
25
+
26
+ * Bugfix when passing hash values to positional arguments.
27
+
28
+ ## 6.0.0 (yanked)
29
+
30
+ * Default arguments! Thanks to [Ola K](https://github.com/lesin). For example: `pattr_initialize [:foo, bar: "default value"]`
31
+
32
+ ## [5.2.0](https://github.com/barsoom/attr_extras/releases/tag/v5.2.0) and earlier
33
+
34
+ Please [see Git history](https://github.com/barsoom/attr_extras/releases).
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- [![Build status](https://secure.travis-ci.org/barsoom/attr_extras.png)](https://travis-ci.org/#!/barsoom/attr_extras/builds)
1
+ [![Gem Version](https://badge.fury.io/rb/attr_extras.svg)](https://badge.fury.io/rb/attr_extras)
2
+ [![Build status](https://secure.travis-ci.org/barsoom/attr_extras.svg)](https://travis-ci.org/#!/barsoom/attr_extras/builds)
2
3
  [![Code Climate](https://codeclimate.com/github/barsoom/attr_extras/badges/gpa.svg)](https://codeclimate.com/github/barsoom/attr_extras)
3
4
 
4
5
  # attr\_extras
@@ -29,7 +30,7 @@ end
29
30
 
30
31
  This nicely complements Ruby's built-in `attr_accessor`, `attr_reader` and `attr_writer`.
31
32
 
32
- Supports positional arguments as well as optional and required hash arguments.
33
+ Supports positional arguments as well as optional and required keyword arguments.
33
34
 
34
35
  Also provides conveniences for creating value objects, method objects, query methods and abstract methods.
35
36
 
@@ -55,14 +56,39 @@ Also provides conveniences for creating value objects, method objects, query met
55
56
 
56
57
  `attr_initialize :foo, :bar` defines an initializer that takes two arguments and assigns `@foo` and `@bar`.
57
58
 
58
- `attr_initialize :foo, [:bar, :baz!]` defines an initializer that takes one regular argument, assigning `@foo`, and one hash argument, assigning `@bar` (optional) and `@baz` (required).
59
+ `attr_initialize :foo, [:bar, :baz!]` defines an initializer that takes one regular argument, assigning `@foo`, and two keyword arguments, assigning `@bar` (optional) and `@baz` (required).
59
60
 
60
- `attr_initialize [:bar, :baz!]` defines an initializer that takes one hash argument, assigning `@bar` (optional) and `@baz` (required).
61
+ `attr_initialize [:bar, :baz!]` defines an initializer that takes two keyword arguments, assigning `@bar` (optional) and `@baz` (required).
61
62
 
62
- If you pass unknown hash arguments, you will get an `ArgumentError`.
63
+ If you pass unknown keyword arguments, you will get an `ArgumentError`.
64
+ If you don't pass required arguments and don't define default value for them, you will get a `KeyError`.
63
65
 
64
66
  `attr_initialize` can also accept a block which will be invoked after initialization. This is useful for e.g. initializing private data as necessary.
65
67
 
68
+ #### Default values
69
+
70
+ Keyword arguments can have default values:
71
+
72
+ `attr_initialize [:bar, baz: "default value"]` defines an initializer that takes two keyword arguments, assigning `@bar` (optional) and `@baz` (optional with default value `"default value"`).
73
+
74
+ Note that default values are evaluated *when the class is loaded* and not on every instantition. So `attr_initialize [time: Time.now]` might not do what you expect.
75
+
76
+ You can always use regular Ruby methods to achieve this:
77
+
78
+ ```
79
+ class Foo
80
+ attr_initialize [:time]
81
+
82
+ private
83
+
84
+ def time
85
+ @time || Time.now
86
+ end
87
+ end
88
+ ```
89
+
90
+ Or just use a regular initializer with default values.
91
+
66
92
 
67
93
  ### `attr_private`
68
94
 
@@ -102,7 +128,7 @@ end
102
128
  Item.new("Pug", 100).price_with_vat # => 125.0
103
129
  ```
104
130
 
105
- [The `attr_initialize` notation](#attr_initialize) for hash arguments is also supported: `pattr_initialize :foo, [:bar, :baz!]`
131
+ [The `attr_initialize` notation](#attr_initialize) for keyword arguments is also supported: `pattr_initialize :foo, [:bar, :baz!]`
106
132
 
107
133
  ### `vattr_initialize`
108
134
  ### `attr_value_initialize`
@@ -127,7 +153,7 @@ Country.new("SE") == Country.new("SE") # => true
127
153
  Country.new("SE").code # => "SE"
128
154
  ```
129
155
 
130
- [The `attr_initialize` notation](#attr_initialize) for hash arguments is also supported: `vattr_initialize :foo, [:bar, :baz!]`
156
+ [The `attr_initialize` notation](#attr_initialize) for keyword arguments is also supported: `vattr_initialize :foo, [:bar, :baz!]`
131
157
 
132
158
 
133
159
  ### `rattr_initialize`
@@ -157,7 +183,7 @@ service = PublishBook.new("A Novel", publisher)
157
183
  service.book_name # => "A Novel"
158
184
  ```
159
185
 
160
- [The `attr_initialize` notation](#attr_initialize) for hash arguments is also supported: `rattr_initialize :foo, [:bar, :baz!]`
186
+ [The `attr_initialize` notation](#attr_initialize) for keyword arguments is also supported: `rattr_initialize :foo, [:bar, :baz!]`
161
187
 
162
188
  ### `aattr_initialize`
163
189
  ### `attr_accessor_initialize`
@@ -185,7 +211,7 @@ client.access_token = "NEW_SECRET"
185
211
  client.access_token # => "NEW_SECRET"
186
212
  ```
187
213
 
188
- [The `attr_initialize` notation](#attr_initialize) for hash arguments and blocks is also supported.
214
+ [The `attr_initialize` notation](#attr_initialize) for keyword arguments and blocks is also supported.
189
215
 
190
216
  ### `static_facade`
191
217
 
@@ -223,7 +249,7 @@ def self.allow?(user)
223
249
  end
224
250
  ```
225
251
 
226
- [The `attr_initialize` notation](#attr_initialize) for hash arguments is also supported: `static_facade :allow?, :user, [:user_agent, :ip!]`
252
+ [The `attr_initialize` notation](#attr_initialize) for keyword arguments is also supported: `static_facade :allow?, :user, [:user_agent, :ip!]`
227
253
 
228
254
  You don't have to specify arguments/readers if you don't want them: just `static_facade :tuesday?` is also valid.
229
255
 
@@ -288,7 +314,7 @@ def self.call(foo)
288
314
  end
289
315
  ```
290
316
 
291
- [The `attr_initialize` notation](#attr_initialize) for hash arguments is also supported: `method_object :foo, [:bar, :baz!]`
317
+ [The `attr_initialize` notation](#attr_initialize) for keyword arguments is also supported: `method_object :foo, [:bar, :baz!]`
292
318
 
293
319
  You don't have to specify arguments/readers if you don't want them: just `method_object` is also valid.
294
320
 
data/Rakefile CHANGED
@@ -19,5 +19,5 @@ Rake::TestTask.new(:test_explicit) do |t|
19
19
  t.test_files = explicit_tests
20
20
  end
21
21
 
22
- task :test => [ :test_implicit, :test_explicit ]
23
- task :default => :test
22
+ task test: [ :test_implicit, :test_explicit ]
23
+ task default: :test
@@ -1,8 +1,8 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/attr_extras/version', __FILE__)
2
+ require File.expand_path("../lib/attr_extras/version", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.authors = ["Henrik Nyh", "Joakim Kolsjö", "Victor Arias"]
5
+ gem.authors = ["Henrik Nyh", "Joakim Kolsjö", "Tomas Skogberg", "Victor Arias", "Ola K"]
6
6
  gem.email = ["henrik@nyh.se"]
7
7
  gem.summary = %q{Takes some boilerplate out of Ruby with methods like attr_initialize.}
8
8
  gem.homepage = "https://github.com/barsoom/attr_extras"
@@ -16,7 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.version = AttrExtras::VERSION
17
17
 
18
18
  gem.add_development_dependency "minitest", ">= 5"
19
- gem.add_development_dependency "m", "~> 1.3.1" # Running individual tests.
19
+ gem.add_development_dependency "m", "~> 1.5.1" # Running individual tests.
20
20
 
21
21
  # For Travis CI.
22
22
  gem.add_development_dependency "rake"
@@ -19,7 +19,7 @@ class AttrExtras::AttrImplement
19
19
  raise ArgumentError, "wrong number of arguments (#{provided_arity} for #{expected_arity})"
20
20
  end
21
21
 
22
- raise AttrExtras::MethodNotImplementedError, "Implement a '#{name}(#{arg_names.join(", ")})' method"
22
+ raise AttrExtras::MethodNotImplementedError, "Implement a#{"n" if name[0].match?(/\A[aeiou]/i)} '#{name}(#{arg_names.join(", ")})' method"
23
23
  else
24
24
  super(name, *args)
25
25
  end
@@ -1,3 +1,5 @@
1
+ require "attr_extras/params_builder"
2
+
1
3
  class AttrExtras::AttrInitialize
2
4
  def initialize(klass, names, block)
3
5
  @klass, @names, @block = klass, names, block
@@ -9,31 +11,29 @@ class AttrExtras::AttrInitialize
9
11
  def apply
10
12
  # The define_method block can't call our methods, so we need to make
11
13
  # things available via local variables.
12
- names = @names
13
14
  block = @block
15
+
16
+ klass_params = AttrExtras::AttrInitialize::ParamsBuilder.new(names)
17
+
14
18
  validate_arity = method(:validate_arity)
15
- set_ivar_from_hash = method(:set_ivar_from_hash)
19
+ validate_args = method(:validate_args)
16
20
 
17
21
  klass.send(:define_method, :initialize) do |*values|
22
+ hash_values = (values[(klass_params.positional_args.length)..-1] || []).inject(:merge) || {}
23
+
18
24
  validate_arity.call(values.length, self.class)
25
+ validate_args.call(values, klass_params)
26
+
27
+ klass_params.default_values.each do |name, default_value|
28
+ instance_variable_set("@#{name}", default_value)
29
+ end
30
+
31
+ klass_params.positional_args.zip(values).each do |name, value|
32
+ instance_variable_set("@#{name}", value)
33
+ end
19
34
 
20
- names.zip(values).each do |name_or_names, value|
21
- if name_or_names.is_a?(Array)
22
- hash = value || {}
23
-
24
- known_keys = name_or_names.map { |name| name.to_s.sub(/!\z/, "").to_sym }
25
- unknown_keys = hash.keys - known_keys
26
- if unknown_keys.any?
27
- raise ArgumentError, "Got unknown keys: #{unknown_keys.inspect}; allowed keys: #{known_keys.inspect}"
28
- end
29
-
30
- name_or_names.each do |name|
31
- set_ivar_from_hash.call(self, name, hash)
32
- end
33
- else
34
- name = name_or_names
35
- instance_variable_set("@#{name}", value)
36
- end
35
+ hash_values.each do |name, value|
36
+ instance_variable_set("@#{name}", value)
37
37
  end
38
38
 
39
39
  if block
@@ -54,15 +54,17 @@ class AttrExtras::AttrInitialize
54
54
  end
55
55
  end
56
56
 
57
- def set_ivar_from_hash(instance, name, hash)
58
- if name.to_s.end_with?("!")
59
- actual_name = name.to_s.chop.to_sym
60
- value = hash.fetch(actual_name)
61
- else
62
- actual_name = name
63
- value = hash[actual_name]
57
+ def validate_args(values, klass_params)
58
+ hash_values = values[(klass_params.positional_args.length)..-1].inject(:merge) || {}
59
+ unknown_keys = hash_values.keys - klass_params.hash_args_names
60
+
61
+ if unknown_keys.any?
62
+ raise ArgumentError, "Got unknown keys: #{unknown_keys.inspect}; allowed keys: #{klass_params.hash_args_names.inspect}"
64
63
  end
65
64
 
66
- instance.instance_variable_set("@#{actual_name}", value)
65
+ missing_args = klass_params.hash_args_required - klass_params.default_values.keys - hash_values.keys
66
+ if missing_args.any?
67
+ raise KeyError, "Missing required keys: #{missing_args.inspect}"
68
+ end
67
69
  end
68
70
  end
@@ -21,9 +21,8 @@ module AttrExtras
21
21
  end
22
22
 
23
23
  def attr_private(*names)
24
- # Need this to avoid "private attribute?" warnings when running
25
- # the full test suite; not sure why exactly.
26
- public
24
+ # Avoid warnings: https://github.com/barsoom/attr_extras/pull/31
25
+ return unless names && names.any?
27
26
 
28
27
  attr_reader(*names)
29
28
  private(*names)
@@ -62,9 +61,17 @@ module AttrExtras
62
61
  alias_method :attr_accessor_initialize, :aattr_initialize
63
62
 
64
63
  def static_facade(method_name_or_names, *names)
65
- Array(method_name_or_names).each do |method_name|
66
- define_singleton_method(method_name) do |*values, &block|
67
- new(*values).public_send(method_name, &block)
64
+ if names.any? { |name| name.is_a?(Array) }
65
+ Array(method_name_or_names).each do |method_name|
66
+ define_singleton_method(method_name) do |*args, **opts, &block|
67
+ new(*args, **opts).public_send(method_name, &block)
68
+ end
69
+ end
70
+ else
71
+ Array(method_name_or_names).each do |method_name|
72
+ define_singleton_method(method_name) do |*args, &block|
73
+ new(*args).public_send(method_name, &block)
74
+ end
68
75
  end
69
76
  end
70
77
 
@@ -0,0 +1,49 @@
1
+ module AttrExtras
2
+ class AttrInitialize
3
+ class ParamsBuilder
4
+ REQUIRED_SIGN = "!".freeze
5
+
6
+ def initialize(names)
7
+ @names = names
8
+ end
9
+
10
+ attr_reader :names
11
+ private :names
12
+
13
+ def positional_args
14
+ @positional_args ||= names.take_while { |name| !name.is_a?(Array) }
15
+ end
16
+
17
+ def hash_args
18
+ @hash_args ||= (names - positional_args).flatten.flat_map { |name|
19
+ name.is_a?(Hash) ? name.keys : name
20
+ }
21
+ end
22
+
23
+ def hash_args_names
24
+ @hash_args_names ||= hash_args.map { |name| remove_required_sign(name) }
25
+ end
26
+
27
+ def hash_args_required
28
+ @hash_args_required ||= hash_args.select { |name| name.to_s.end_with?(REQUIRED_SIGN) }.
29
+ map { |name| remove_required_sign(name) }
30
+ end
31
+
32
+ def default_values
33
+ @default_values ||= begin
34
+ default_values_hash = names.flatten.select { |name| name.is_a?(Hash) }.inject(:merge) || {}
35
+
36
+ default_values_hash.map { |name, value|
37
+ [ remove_required_sign(name), value ]
38
+ }.to_h
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def remove_required_sign(name)
45
+ name.to_s.sub(/#{REQUIRED_SIGN}\z/, "").to_sym
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,5 +1,7 @@
1
1
  module AttrExtras::Utils
2
2
  def self.flat_names(names)
3
- names.flatten.map { |x| x.to_s.sub(/!\z/, "") }
3
+ names.flatten.
4
+ flat_map { |x| x.is_a?(Hash) ? x.keys : x }.
5
+ map { |x| x.to_s.sub(/!\z/, "") }
4
6
  end
5
7
  end
@@ -1,3 +1,3 @@
1
1
  module AttrExtras
2
- VERSION = "5.2.0"
2
+ VERSION = "6.2.4"
3
3
  end
@@ -8,7 +8,7 @@ describe Object, ".aattr_initialize" do
8
8
 
9
9
  example = klass.new("Foo", "Bar")
10
10
 
11
- example.foo.must_equal "Foo"
11
+ _(example.foo).must_equal "Foo"
12
12
  end
13
13
 
14
14
  it "creates public writers" do
@@ -19,17 +19,27 @@ describe Object, ".aattr_initialize" do
19
19
  example = klass.new("Foo", "Bar")
20
20
  example.foo = "Baz"
21
21
 
22
- example.foo.must_equal "Baz"
22
+ _(example.foo).must_equal "Baz"
23
23
  end
24
24
 
25
25
  it "works with hash ivars" do
26
26
  klass = Class.new do
27
- aattr_initialize :foo, [:bar, :baz!]
27
+ aattr_initialize :foo, [ :bar, :baz! ]
28
28
  end
29
29
 
30
- example = klass.new("Foo", :bar => "Bar", :baz => "Baz")
30
+ example = klass.new("Foo", bar: "Bar", baz: "Baz")
31
31
 
32
- example.baz.must_equal "Baz"
32
+ _(example.baz).must_equal "Baz"
33
+ end
34
+
35
+ it "works with hash ivars and default values" do
36
+ klass = Class.new do
37
+ aattr_initialize :foo, [ bar: "Bar", baz!: "Baz" ]
38
+ end
39
+
40
+ example = klass.new("Foo")
41
+
42
+ _(example.baz).must_equal "Baz"
33
43
  end
34
44
 
35
45
  it "accepts a block for initialization" do
@@ -43,7 +53,7 @@ describe Object, ".aattr_initialize" do
43
53
 
44
54
  example = klass.new("expected")
45
55
 
46
- example.copy.must_equal "expected"
56
+ _(example.copy).must_equal "expected"
47
57
  end
48
58
 
49
59
  it "accepts the alias attr_accessor_initialize" do
@@ -53,6 +63,6 @@ describe Object, ".aattr_initialize" do
53
63
 
54
64
  example = klass.new("Foo", "Bar")
55
65
 
56
- example.foo.must_equal "Foo"
66
+ _(example.foo).must_equal "Foo"
57
67
  end
58
68
  end
@@ -15,6 +15,6 @@ describe Object, ".attr_id_query" do
15
15
  end
16
16
 
17
17
  it "requires a trailing questionmark" do
18
- lambda { Object.attr_id_query(:foo) }.must_raise ArgumentError
18
+ _(lambda { Object.attr_id_query(:foo) }).must_raise ArgumentError
19
19
  end
20
20
  end
@@ -7,8 +7,8 @@ describe Object, ".attr_implement" do
7
7
  end
8
8
 
9
9
  example = klass.new
10
- exception = lambda { example.foo }.must_raise AttrExtras::MethodNotImplementedError
11
- exception.message.must_equal "Implement a 'foo()' method"
10
+ exception = _(lambda { example.foo }).must_raise AttrExtras::MethodNotImplementedError
11
+ _(exception.message).must_equal "Implement a 'foo()' method"
12
12
  end
13
13
 
14
14
  it "allows specifying arity and argument names" do
@@ -18,10 +18,10 @@ describe Object, ".attr_implement" do
18
18
 
19
19
  example = klass.new
20
20
 
21
- exception = lambda { example.foo(1, 2) }.must_raise AttrExtras::MethodNotImplementedError
22
- exception.message.must_equal "Implement a 'foo(name, age)' method"
21
+ exception = _(lambda { example.foo(1, 2) }).must_raise AttrExtras::MethodNotImplementedError
22
+ _(exception.message).must_equal "Implement a 'foo(name, age)' method"
23
23
 
24
- lambda { example.foo }.must_raise ArgumentError
24
+ _(lambda { example.foo }).must_raise ArgumentError
25
25
  end
26
26
 
27
27
  it "does not raise if method is implemented in a subclass" do
@@ -35,7 +35,7 @@ describe Object, ".attr_implement" do
35
35
  end
36
36
  end
37
37
 
38
- subklass.new.foo.must_equal "bar"
38
+ _(subklass.new.foo).must_equal "bar"
39
39
  end
40
40
 
41
41
  # E.g. when Active Record defines column query methods like "admin?"
@@ -55,7 +55,7 @@ describe Object, ".attr_implement" do
55
55
  include foo_interface
56
56
  end
57
57
 
58
- klass.new.foo.must_equal "bar"
58
+ _(klass.new.foo).must_equal "bar"
59
59
  end
60
60
 
61
61
  it "does not mess up missing-method handling" do
@@ -63,7 +63,17 @@ describe Object, ".attr_implement" do
63
63
  attr_implement :foo
64
64
  end
65
65
 
66
- lambda { klass.new.some_other_method }.must_raise NoMethodError
66
+ _(lambda { klass.new.some_other_method }).must_raise NoMethodError
67
+ end
68
+
69
+ it "says 'an' if followed by a vowel" do
70
+ klass = Class.new do
71
+ attr_implement :ear
72
+ end
73
+
74
+ example = klass.new
75
+ exception = _(lambda { example.ear }).must_raise AttrExtras::MethodNotImplementedError
76
+ _(exception.message).must_equal "Implement an 'ear()' method"
67
77
  end
68
78
  end
69
79
 
@@ -73,9 +83,9 @@ describe Object, ".cattr_implement" do
73
83
  cattr_implement :foo, [:name, :age]
74
84
  end
75
85
 
76
- exception = lambda { klass.foo(1, 2) }.must_raise AttrExtras::MethodNotImplementedError
77
- exception.message.must_equal "Implement a 'foo(name, age)' method"
86
+ exception = _(lambda { klass.foo(1, 2) }).must_raise AttrExtras::MethodNotImplementedError
87
+ _(exception.message).must_equal "Implement a 'foo(name, age)' method"
78
88
 
79
- lambda { klass.foo }.must_raise ArgumentError
89
+ _(lambda { klass.foo }).must_raise ArgumentError
80
90
  end
81
91
  end
@@ -13,13 +13,13 @@ describe Object, ".attr_initialize" do
13
13
 
14
14
  it "creates an initializer setting those instance variables" do
15
15
  example = klass.new("Foo", "Bar")
16
- example.instance_variable_get("@foo").must_equal "Foo"
17
- example.instance_variable_get("@bar").must_equal "Bar"
16
+ _(example.instance_variable_get("@foo")).must_equal "Foo"
17
+ _(example.instance_variable_get("@bar")).must_equal "Bar"
18
18
  end
19
19
 
20
20
  it "requires all arguments" do
21
- exception = lambda { klass.new("Foo") }.must_raise ArgumentError
22
- exception.message.must_equal "wrong number of arguments (1 for 2) for ExampleClass initializer"
21
+ exception = _(lambda { klass.new("Foo") }).must_raise ArgumentError
22
+ _(exception.message).must_equal "wrong number of arguments (1 for 2) for ExampleClass initializer"
23
23
  end
24
24
 
25
25
  it "can set ivars from a hash" do
@@ -27,10 +27,24 @@ describe Object, ".attr_initialize" do
27
27
  attr_initialize :foo, [:bar, :baz]
28
28
  end
29
29
 
30
- example = klass.new("Foo", :bar => "Bar", :baz => "Baz")
31
- example.instance_variable_get("@foo").must_equal "Foo"
32
- example.instance_variable_get("@bar").must_equal "Bar"
33
- example.instance_variable_get("@baz").must_equal "Baz"
30
+ example = klass.new("Foo", bar: "Bar", baz: "Baz")
31
+ _(example.instance_variable_get("@foo")).must_equal "Foo"
32
+ _(example.instance_variable_get("@bar")).must_equal "Bar"
33
+ _(example.instance_variable_get("@baz")).must_equal "Baz"
34
+ end
35
+
36
+ it "can set default values for keyword arguments" do
37
+ klass = Class.new do
38
+ attr_initialize :foo, [:bar, baz: "default baz"]
39
+ end
40
+
41
+ example = klass.new("Foo", bar: "Bar")
42
+ _(example.instance_variable_get("@foo")).must_equal "Foo"
43
+ _(example.instance_variable_get("@bar")).must_equal "Bar"
44
+ _(example.instance_variable_get("@baz")).must_equal "default baz"
45
+
46
+ example = klass.new("Foo", bar: "Bar", baz: "Baz")
47
+ _(example.instance_variable_get("@baz")).must_equal "Baz"
34
48
  end
35
49
 
36
50
  it "treats hash values as optional" do
@@ -38,11 +52,11 @@ describe Object, ".attr_initialize" do
38
52
  attr_initialize :foo, [:bar, :baz]
39
53
  end
40
54
 
41
- example = klass.new("Foo", :bar => "Bar")
42
- example.instance_variable_get("@baz").must_equal nil
55
+ example = klass.new("Foo", bar: "Bar")
56
+ _(example.instance_variable_defined?("@baz")).must_equal false
43
57
 
44
58
  example = klass.new("Foo")
45
- example.instance_variable_get("@bar").must_equal nil
59
+ _(example.instance_variable_defined?("@bar")).must_equal false
46
60
  end
47
61
 
48
62
  it "can require hash values" do
@@ -50,10 +64,10 @@ describe Object, ".attr_initialize" do
50
64
  attr_initialize [:optional, :required!]
51
65
  end
52
66
 
53
- example = klass.new(:required => "X")
54
- example.instance_variable_get("@required").must_equal "X"
67
+ example = klass.new(required: "X")
68
+ _(example.instance_variable_get("@required")).must_equal "X"
55
69
 
56
- lambda { klass.new(:optional => "X") }.must_raise KeyError
70
+ _(lambda { klass.new(optional: "X") }).must_raise KeyError
57
71
  end
58
72
 
59
73
  it "complains about unknown hash values" do
@@ -62,10 +76,44 @@ describe Object, ".attr_initialize" do
62
76
  end
63
77
 
64
78
  # Should not raise.
65
- klass.new("Foo", :bar => "Bar", :baz => "Baz")
79
+ klass.new("Foo", bar: "Bar", baz: "Baz")
80
+
81
+ exception = _(lambda { klass.new("Foo", bar: "Bar", baz: "Baz", hello: "Hello") }).must_raise ArgumentError
82
+ _(exception.message).must_include "[:hello]"
83
+ end
84
+
85
+ # Regression.
86
+ it "assigns hash values to positional arguments even when there's also hash arguments" do
87
+ klass = Class.new do
88
+ attr_initialize :foo, [:bar]
89
+ end
90
+
91
+ # Should not raise.
92
+ klass.new({ inside_foo: 123 }, bar: 456)
93
+ end
94
+
95
+ # Regression.
96
+ it "only looks at hash arguments when determining missing required keys" do
97
+ klass = Class.new do
98
+ attr_initialize :foo, [:bar!]
99
+ end
100
+
101
+ # Provides a hash to "foo" but does not provide "bar".
102
+ exception = _(lambda { klass.new({ bar: 123 }) }).must_raise KeyError
103
+ _(exception.message).must_include "[:bar]"
104
+ end
105
+
106
+ # Regression.
107
+ it "doesn't store hash values to positional arguments as ivars" do
108
+ klass = Class.new do
109
+ attr_initialize :foo
110
+ attr_reader :foo
111
+ end
112
+
113
+ # Should not raise.
114
+ example = klass.new({ "invalid.ivar.name" => 123 })
66
115
 
67
- exception = lambda { klass.new("Foo", :bar => "Bar", :baz => "Baz", :hello => "Hello") }.must_raise ArgumentError
68
- exception.message.must_include "[:hello]"
116
+ _(example.foo).must_equal({ "invalid.ivar.name" => 123 })
69
117
  end
70
118
 
71
119
  it "accepts a block for initialization" do
@@ -79,6 +127,6 @@ describe Object, ".attr_initialize" do
79
127
 
80
128
  example = klass.new("expected")
81
129
 
82
- example.copy.must_equal "expected"
130
+ _(example.copy).must_equal "expected"
83
131
  end
84
132
  end
@@ -11,8 +11,8 @@ describe Object, ".attr_private" do
11
11
  example = klass.new
12
12
  example.instance_variable_set("@foo", "Foo")
13
13
  example.instance_variable_set("@bar", "Bar")
14
- example.send(:foo).must_equal "Foo"
15
- example.send(:bar).must_equal "Bar"
16
- lambda { example.foo }.must_raise NoMethodError
14
+ _(example.send(:foo)).must_equal "Foo"
15
+ _(example.send(:bar)).must_equal "Bar"
16
+ _(lambda { example.foo }).must_raise NoMethodError
17
17
  end
18
18
  end
@@ -14,6 +14,6 @@ describe Object, ".attr_query" do
14
14
  end
15
15
 
16
16
  it "requires a trailing questionmark" do
17
- lambda { Object.attr_query(:foo) }.must_raise ArgumentError
17
+ _(lambda { Object.attr_query(:foo) }).must_raise ArgumentError
18
18
  end
19
19
  end
@@ -9,7 +9,7 @@ describe Object, ".attr_value" do
9
9
 
10
10
  example = klass.new
11
11
  example.instance_variable_set("@foo", "Foo")
12
- example.foo.must_equal "Foo"
12
+ _(example.foo).must_equal "Foo"
13
13
  end
14
14
 
15
15
  it "does not create writers" do
@@ -17,7 +17,7 @@ describe Object, ".attr_value" do
17
17
  attr_value :foo
18
18
  end
19
19
 
20
- lambda { klass.new.foo = "new value" }.must_raise NoMethodError
20
+ _(lambda { klass.new.foo = "new value" }).must_raise NoMethodError
21
21
  end
22
22
 
23
23
  describe "object equality" do
@@ -92,21 +92,21 @@ describe Object, ".attr_value" do
92
92
  klass1_bar = klass1.new("Bar")
93
93
  klass2_foo = klass2.new("Foo")
94
94
 
95
- klass1_foo.hash.must_equal klass1_foo2.hash
96
- klass1_foo.hash.wont_equal klass1_bar.hash
97
- klass1_foo.hash.wont_equal klass2_foo.hash
95
+ _(klass1_foo.hash).must_equal klass1_foo2.hash
96
+ _(klass1_foo.hash).wont_equal klass1_bar.hash
97
+ _(klass1_foo.hash).wont_equal klass2_foo.hash
98
98
 
99
99
  assert klass1_foo.eql?(klass1_foo2), "Examples should be 'eql?'"
100
100
  refute klass1_foo.eql?(klass1_bar), "Examples should not be 'eql?'"
101
101
  refute klass1_foo.eql?(klass2_foo), "Examples should not be 'eql?'"
102
102
 
103
- Set[klass1_foo, klass1_foo2, klass1_bar, klass2_foo].length.must_equal 3
103
+ _(Set[klass1_foo, klass1_foo2, klass1_bar, klass2_foo].length).must_equal 3
104
104
 
105
105
  hash = {}
106
106
  hash[klass1_foo] = :awyeah
107
107
  hash[klass1_bar] = :wat
108
108
  hash[klass2_foo] = :nooooo
109
- hash[klass1_foo2].must_equal :awyeah
109
+ _(hash[klass1_foo2]).must_equal :awyeah
110
110
  end
111
111
  end
112
112
  end
@@ -0,0 +1,53 @@
1
+ require "spec_helper"
2
+
3
+ describe AttrExtras::AttrInitialize::ParamsBuilder do
4
+ subject { AttrExtras::AttrInitialize::ParamsBuilder.new(names) }
5
+
6
+ describe "when positional and hash params are present" do
7
+ let(:names) { [ :foo, :bar, [ :baz, :qux!, quux: "Quux" ]] }
8
+
9
+ it "properly devides params by the type" do
10
+ _(subject.positional_args).must_equal [ :foo, :bar ]
11
+ _(subject.hash_args).must_equal [ :baz, :qux!, :quux ]
12
+ _(subject.hash_args_names).must_equal [ :baz, :qux, :quux ]
13
+ _(subject.hash_args_required).must_equal [ :qux ]
14
+ _(subject.default_values).must_equal({ quux: "Quux" })
15
+ end
16
+ end
17
+
18
+ describe "when only positional params are present" do
19
+ let(:names) { [ :foo, :bar] }
20
+
21
+ it "properly devides params by the type" do
22
+ _(subject.positional_args).must_equal [ :foo, :bar ]
23
+ _(subject.hash_args).must_be_empty
24
+ _(subject.hash_args_names).must_be_empty
25
+ _(subject.hash_args_required).must_be_empty
26
+ _(subject.default_values).must_be_empty
27
+ end
28
+ end
29
+
30
+ describe "when only hash params are present" do
31
+ let(:names) { [[ { baz: "Baz" }, :qux!, { quux: "Quux" } ]] }
32
+
33
+ it "properly devides params by the type" do
34
+ _(subject.positional_args).must_be_empty
35
+ _(subject.hash_args).must_equal [ :baz, :qux!, :quux ]
36
+ _(subject.hash_args_names).must_equal [ :baz, :qux, :quux ]
37
+ _(subject.hash_args_required).must_equal [ :qux ]
38
+ _(subject.default_values).must_equal({ quux: "Quux", baz: "Baz" })
39
+ end
40
+ end
41
+
42
+ describe "when params are empty" do
43
+ let(:names) { [] }
44
+
45
+ it "properly devides params by the type" do
46
+ _(subject.positional_args).must_be_empty
47
+ _(subject.hash_args).must_be_empty
48
+ _(subject.hash_args_names).must_be_empty
49
+ _(subject.hash_args_required).must_be_empty
50
+ _(subject.default_values).must_be_empty
51
+ end
52
+ end
53
+ end
@@ -7,7 +7,7 @@ describe Object, ".pattr_initialize" do
7
7
  end
8
8
 
9
9
  example = klass.new("Foo", "Bar")
10
- example.send(:foo).must_equal "Foo"
10
+ _(example.send(:foo)).must_equal "Foo"
11
11
  end
12
12
 
13
13
  it "works with hash ivars" do
@@ -15,8 +15,17 @@ describe Object, ".pattr_initialize" do
15
15
  pattr_initialize :foo, [:bar, :baz!]
16
16
  end
17
17
 
18
- example = klass.new("Foo", :bar => "Bar", :baz => "Baz")
19
- example.send(:baz).must_equal "Baz"
18
+ example = klass.new("Foo", bar: "Bar", baz: "Baz")
19
+ _(example.send(:baz)).must_equal "Baz"
20
+ end
21
+
22
+ it "works with hash ivars and default values" do
23
+ klass = Class.new do
24
+ pattr_initialize :foo, [ bar: "Bar", baz!: "Baz" ]
25
+ end
26
+
27
+ example = klass.new("Foo")
28
+ _(example.send(:baz)).must_equal "Baz"
20
29
  end
21
30
 
22
31
  it "can reference private initializer methods in an initializer block" do
@@ -30,7 +39,7 @@ describe Object, ".pattr_initialize" do
30
39
 
31
40
  example = klass.new("expected")
32
41
 
33
- example.copy.must_equal "expected"
42
+ _(example.copy).must_equal "expected"
34
43
  end
35
44
 
36
45
  it "accepts the alias attr_private_initialize" do
@@ -39,6 +48,6 @@ describe Object, ".pattr_initialize" do
39
48
  end
40
49
 
41
50
  example = klass.new("Foo", "Bar")
42
- example.send(:foo).must_equal "Foo"
51
+ _(example.send(:foo)).must_equal "Foo"
43
52
  end
44
53
  end
@@ -7,16 +7,25 @@ describe Object, ".rattr_initialize" do
7
7
  end
8
8
 
9
9
  example = klass.new("Foo", "Bar")
10
- example.public_send(:foo).must_equal "Foo"
10
+ _(example.public_send(:foo)).must_equal "Foo"
11
11
  end
12
12
 
13
13
  it "works with hash ivars" do
14
14
  klass = Class.new do
15
- rattr_initialize :foo, [:bar, :baz!]
15
+ rattr_initialize :foo, [ :bar, :baz! ]
16
16
  end
17
17
 
18
- example = klass.new("Foo", :bar => "Bar", :baz => "Baz")
19
- example.public_send(:baz).must_equal "Baz"
18
+ example = klass.new("Foo", bar: "Bar", baz: "Baz")
19
+ _(example.public_send(:baz)).must_equal "Baz"
20
+ end
21
+
22
+ it "works with hash ivars and default values" do
23
+ klass = Class.new do
24
+ rattr_initialize :foo, [ bar: "Bar", baz!: "Baz" ]
25
+ end
26
+
27
+ example = klass.new("Foo")
28
+ _(example.send(:baz)).must_equal "Baz"
20
29
  end
21
30
 
22
31
  it "accepts the alias attr_reader_initialize" do
@@ -25,6 +34,6 @@ describe Object, ".rattr_initialize" do
25
34
  end
26
35
 
27
36
  example = klass.new("Foo", "Bar")
28
- example.public_send(:foo).must_equal "Foo"
37
+ _(example.public_send(:foo)).must_equal "Foo"
29
38
  end
30
39
  end
@@ -56,4 +56,34 @@ describe Object, ".static_facade" do
56
56
 
57
57
  assert klass.foo { :bar } == :bar
58
58
  end
59
+
60
+ it "does not blow up when the class method is called with an empty hash" do
61
+ klass = Class.new do
62
+ static_facade :foo,
63
+ :value
64
+
65
+ def foo
66
+ end
67
+ end
68
+
69
+ refute_raises_anything { klass.foo({}) }
70
+ end
71
+
72
+ it "does not emit warnings when the initializer is overridden with more keyword arguments" do
73
+ superklass = Class.new do
74
+ static_facade :something, [ :foo!, :bar! ]
75
+
76
+ def something
77
+ end
78
+ end
79
+
80
+ klass = Class.new(superklass) do
81
+ def initialize(extra:, **rest)
82
+ super(**rest)
83
+ @extra = extra
84
+ end
85
+ end
86
+
87
+ refute_warnings_emitted { klass.something(foo: 1, bar: 2, extra: "yay") }
88
+ end
59
89
  end
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+
3
+ describe AttrExtras::Utils do
4
+ describe ".flat_names" do
5
+ subject { AttrExtras::Utils.flat_names(names) }
6
+
7
+ it "strips any bangs from a flat list of arguments" do
8
+ _(AttrExtras::Utils.flat_names([ :foo, :bar! ])).must_equal [ "foo", "bar" ]
9
+ end
10
+
11
+ it "flattens hash arguments and strips any bangs" do
12
+ _(AttrExtras::Utils.flat_names([ :foo, [ :bar, :baz! ] ])).must_equal [ "foo", "bar", "baz" ]
13
+ end
14
+
15
+ it "flattens hash arguments with defaults and strips any bangs" do
16
+ _(AttrExtras::Utils.flat_names([ :foo, [ bar: "Bar", baz!: "Baz"] ])).must_equal [ "foo", "bar", "baz" ]
17
+ end
18
+ end
19
+ end
@@ -9,8 +9,8 @@ describe Object, ".vattr_initialize" do
9
9
  example1 = klass.new("Foo", "Bar")
10
10
  example2 = klass.new("Foo", "Bar")
11
11
 
12
- example1.foo.must_equal "Foo"
13
- example1.must_equal example2
12
+ _(example1.foo).must_equal "Foo"
13
+ _(example1).must_equal example2
14
14
  end
15
15
 
16
16
  it "works with hash ivars" do
@@ -18,10 +18,21 @@ describe Object, ".vattr_initialize" do
18
18
  vattr_initialize :foo, [:bar, :baz!]
19
19
  end
20
20
 
21
- example1 = klass.new("Foo", :bar => "Bar", :baz => "Baz")
22
- example2 = klass.new("Foo", :bar => "Bar", :baz => "Baz")
23
- example1.baz.must_equal "Baz"
24
- example1.must_equal example2
21
+ example1 = klass.new("Foo", bar: "Bar", baz: "Baz")
22
+ example2 = klass.new("Foo", bar: "Bar", baz: "Baz")
23
+ _(example1.baz).must_equal "Baz"
24
+ _(example1).must_equal example2
25
+ end
26
+
27
+ it "works with hash ivars and default values" do
28
+ klass = Class.new do
29
+ vattr_initialize :foo, [ bar: "Bar", baz!: "Baz" ]
30
+ end
31
+
32
+ example1 = klass.new("Foo")
33
+ example2 = klass.new("Foo")
34
+ _(example1.baz).must_equal "Baz"
35
+ _(example1).must_equal example2
25
36
  end
26
37
 
27
38
  it "can accept an initializer block" do
@@ -34,7 +45,7 @@ describe Object, ".vattr_initialize" do
34
45
 
35
46
  klass.new("expected")
36
47
 
37
- called.must_equal true
48
+ _(called).must_equal true
38
49
  end
39
50
 
40
51
  it "accepts the alias attr_value_initialize" do
@@ -45,7 +56,7 @@ describe Object, ".vattr_initialize" do
45
56
  example1 = klass.new("Foo", "Bar")
46
57
  example2 = klass.new("Foo", "Bar")
47
58
 
48
- example1.foo.must_equal "Foo"
49
- example1.must_equal example2
59
+ _(example1.foo).must_equal "Foo"
60
+ _(example1).must_equal example2
50
61
  end
51
62
  end
@@ -10,6 +10,6 @@ describe AttrExtras do
10
10
  include mod
11
11
  end
12
12
 
13
- klass.new("Hello").send(:name).must_equal "Hello"
13
+ _(klass.new("Hello").send(:name)).must_equal "Hello"
14
14
  end
15
15
  end
@@ -2,3 +2,20 @@ require "minitest/autorun"
2
2
  require "minitest/pride"
3
3
 
4
4
  $: << File.dirname(__FILE__) + "/../lib"
5
+
6
+ Minitest::Test.class_eval do
7
+ def refute_warnings_emitted(&block)
8
+ _, stderr = capture_io(&block)
9
+
10
+ assert stderr.empty?, -> do
11
+ warnings = stderr.strip.split("\n").map { |line| " #{line}" }.join("\n")
12
+ "Expected no warnings to be emitted, but these ones were:\n\n#{warnings}"
13
+ end
14
+ end
15
+
16
+ def refute_raises_anything
17
+ yield
18
+ rescue => error
19
+ flunk "Expected no error to be raised, but got #{error.class} (#{error.message})."
20
+ end
21
+ end
metadata CHANGED
@@ -1,16 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attr_extras
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 6.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henrik Nyh
8
8
  - Joakim Kolsjö
9
+ - Tomas Skogberg
9
10
  - Victor Arias
11
+ - Ola K
10
12
  autorequire:
11
13
  bindir: bin
12
14
  cert_chain: []
13
- date: 2017-01-31 00:00:00.000000000 Z
15
+ date: 2020-06-08 00:00:00.000000000 Z
14
16
  dependencies:
15
17
  - !ruby/object:Gem::Dependency
16
18
  name: minitest
@@ -32,14 +34,14 @@ dependencies:
32
34
  requirements:
33
35
  - - "~>"
34
36
  - !ruby/object:Gem::Version
35
- version: 1.3.1
37
+ version: 1.5.1
36
38
  type: :development
37
39
  prerelease: false
38
40
  version_requirements: !ruby/object:Gem::Requirement
39
41
  requirements:
40
42
  - - "~>"
41
43
  - !ruby/object:Gem::Version
42
- version: 1.3.1
44
+ version: 1.5.1
43
45
  - !ruby/object:Gem::Dependency
44
46
  name: rake
45
47
  requirement: !ruby/object:Gem::Requirement
@@ -63,6 +65,7 @@ extra_rdoc_files: []
63
65
  files:
64
66
  - ".gitignore"
65
67
  - ".travis.yml"
68
+ - CHANGELOG.md
66
69
  - Gemfile
67
70
  - LICENSE.txt
68
71
  - README.md
@@ -74,6 +77,7 @@ files:
74
77
  - lib/attr_extras/attr_query.rb
75
78
  - lib/attr_extras/attr_value.rb
76
79
  - lib/attr_extras/explicit.rb
80
+ - lib/attr_extras/params_builder.rb
77
81
  - lib/attr_extras/utils.rb
78
82
  - lib/attr_extras/version.rb
79
83
  - script/test
@@ -86,9 +90,11 @@ files:
86
90
  - spec/attr_extras/attr_value_spec.rb
87
91
  - spec/attr_extras/explicit_spec.rb
88
92
  - spec/attr_extras/method_object_spec.rb
93
+ - spec/attr_extras/params_builder_spec.rb
89
94
  - spec/attr_extras/pattr_initialize_spec.rb
90
95
  - spec/attr_extras/rattr_initialize_spec.rb
91
96
  - spec/attr_extras/static_facade_spec.rb
97
+ - spec/attr_extras/utils_spec.rb
92
98
  - spec/attr_extras/vattr_initialize_spec.rb
93
99
  - spec/attr_extras_spec.rb
94
100
  - spec/spec_helper.rb
@@ -112,8 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
118
  - !ruby/object:Gem::Version
113
119
  version: '0'
114
120
  requirements: []
115
- rubyforge_project:
116
- rubygems_version: 2.5.2
121
+ rubygems_version: 3.1.2
117
122
  signing_key:
118
123
  specification_version: 4
119
124
  summary: Takes some boilerplate out of Ruby with methods like attr_initialize.
@@ -127,9 +132,11 @@ test_files:
127
132
  - spec/attr_extras/attr_value_spec.rb
128
133
  - spec/attr_extras/explicit_spec.rb
129
134
  - spec/attr_extras/method_object_spec.rb
135
+ - spec/attr_extras/params_builder_spec.rb
130
136
  - spec/attr_extras/pattr_initialize_spec.rb
131
137
  - spec/attr_extras/rattr_initialize_spec.rb
132
138
  - spec/attr_extras/static_facade_spec.rb
139
+ - spec/attr_extras/utils_spec.rb
133
140
  - spec/attr_extras/vattr_initialize_spec.rb
134
141
  - spec/attr_extras_spec.rb
135
142
  - spec/spec_helper.rb