smart_properties 1.14.0 → 1.16.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 845948d63c5a647048b44f96e405808bdf3697430323a9ebf29b57c13ed157c9
4
- data.tar.gz: be7cc4c03ab7bcba3dbdaea8a04676ada44f50f07d3f5a323926098af6560c97
3
+ metadata.gz: 3d970dbdc63b4c78e01ec7f569bd00d9c37a61c672f97d084a5a01b205c80159
4
+ data.tar.gz: 0db3dca56f41fafc8c96cef4c2ebd54068ce505101a3f949549724522b88bf3f
5
5
  SHA512:
6
- metadata.gz: fc5370fce67258915440c7e6fe3b02b1207d04ab1716be3f22b478f7ebf1d22807115b2f7aa7639b82030ad4309a69941606b20d6a64815e9afacd4fb48cdafb
7
- data.tar.gz: 003da1d3daab182873e5ccd4ba6359707309fc06161f095c52803cfd6e1d3cb055c0baa59886aaed8b51f3eb5d05d4c6efdfc6188445a6e6de8d4c48a607b8ec
6
+ metadata.gz: 186ffbca4f484bd2dcf39fef28bf49523323d195c515567266dfe1c1166b513a426b8da3548a350025d11ca4188984ad312302198ec8d365de00d120b93921a5
7
+ data.tar.gz: dffb5d926bc985cf751d654782d152acb38ef1a9b1febd9ccec9bb2589fb61e55b6f471d83d82f106d2f504b1276c2f00166ff18b2f3ea48fc550687d60cf5de
@@ -0,0 +1,24 @@
1
+ name: Testing
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ test:
12
+
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - name: Set up Ruby
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: 2.6
21
+ - name: Install dependencies
22
+ run: bundle install
23
+ - name: Run tests
24
+ run: bundle exec rake
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.2
1
+ 2.6.6
data/README.md CHANGED
@@ -177,7 +177,7 @@ often. These validations can be found in the `SmartProperties::Validations` modu
177
177
 
178
178
  ```ruby
179
179
  class Article
180
- property :view_count, accepts: Ancestor.must_be(Number)
180
+ property :view_count, accepts: Ancestor.must_be(type: Number)
181
181
  end
182
182
  ```
183
183
 
@@ -1,6 +1,7 @@
1
1
  module SmartProperties
2
2
  class Property
3
3
  MODULE_REFERENCE = :"@_smart_properties_method_scope"
4
+ ALLOWED_DEFAULT_CLASSES = [Proc, Numeric, String, Range, TrueClass, FalseClass, NilClass, Symbol, Module].freeze
4
5
 
5
6
  attr_reader :name
6
7
  attr_reader :converter
@@ -27,6 +28,11 @@ module SmartProperties
27
28
 
28
29
  @instance_variable_name = :"@#{name}"
29
30
 
31
+ unless ALLOWED_DEFAULT_CLASSES.any? { |cls| @default.is_a?(cls) }
32
+ raise ConfigurationError, "Default attribute value #{@default.inspect} cannot be specified as literal, "\
33
+ "use the syntax `default: -> { ... }` instead."
34
+ end
35
+
30
36
  unless attrs.empty?
31
37
  raise ConfigurationError, "SmartProperties do not support the following configuration options: #{attrs.keys.map { |m| m.to_s }.sort.join(', ')}."
32
38
  end
@@ -66,7 +72,7 @@ module SmartProperties
66
72
  end
67
73
 
68
74
  def default(scope)
69
- @default.kind_of?(Proc) ? scope.instance_exec(&@default) : @default
75
+ @default.kind_of?(Proc) ? scope.instance_exec(&@default) : @default.dup
70
76
  end
71
77
 
72
78
  def accepts?(value, scope)
@@ -151,12 +157,19 @@ module SmartProperties
151
157
  rescue NoMethodError => error
152
158
  # BasicObject does not respond to #nil? by default, so we need to double
153
159
  # check if somebody implemented it and it fails internally or if the
154
- # error occured because the method is actually not present. In the former
155
- # case, we want to raise the exception because there is something wrong
156
- # with the implementation of object#nil?. In the latter case we treat the
157
- # object as truthy because we don't know better.
158
- raise error if (class << object; self; end).public_instance_methods.include?(:nil?)
159
- false
160
+ # error occured because the method is actually not present.
161
+
162
+ # This is a workaround for the fact that #singleton_class is defined on Object, but not BasicObject.
163
+ the_singleton_class = (class << object; self; end)
164
+
165
+ if the_singleton_class.public_instance_methods.include?(:nil?)
166
+ # object defines #nil?, but it raised NoMethodError,
167
+ # something is wrong with the implementation, so raise the exception.
168
+ raise error
169
+ else
170
+ # treat the object as truthy because we don't know better.
171
+ false
172
+ end
160
173
  end
161
174
  end
162
175
  end
@@ -5,18 +5,19 @@ module SmartProperties
5
5
  attr_reader :parent
6
6
 
7
7
  def self.for(scope)
8
- parent = scope.ancestors[1..-1].find do |ancestor|
8
+ parents = scope.ancestors[1..-1].select do |ancestor|
9
9
  ancestor.ancestors.include?(SmartProperties) &&
10
10
  ancestor != scope &&
11
11
  ancestor != SmartProperties
12
12
  end
13
13
 
14
- if parent.nil?
15
- new
16
- else
17
- parent.properties.register(collection = new)
18
- collection
14
+ collection = new
15
+
16
+ parents.reverse.each do |parent|
17
+ parent.properties.register(collection)
19
18
  end
19
+
20
+ collection
20
21
  end
21
22
 
22
23
  def initialize
@@ -75,7 +76,7 @@ module SmartProperties
75
76
  end
76
77
 
77
78
  def refresh(parent_collection)
78
- @collection_with_parent_collection = parent_collection.merge(collection)
79
+ @collection_with_parent_collection.merge!(parent_collection)
79
80
  notify_children
80
81
  nil
81
82
  end
@@ -1,3 +1,3 @@
1
1
  module SmartProperties
2
- VERSION = "1.14.0"
2
+ VERSION = "1.16.2"
3
3
  end
@@ -91,6 +91,13 @@ module SmartProperties
91
91
  protected :property!
92
92
  end
93
93
 
94
+ module ModuleMethods
95
+ def included(target)
96
+ super
97
+ target.include(SmartProperties)
98
+ end
99
+ end
100
+
94
101
  class << self
95
102
  private
96
103
 
@@ -102,6 +109,7 @@ module SmartProperties
102
109
  #
103
110
  def included(base)
104
111
  base.extend(ClassMethods)
112
+ base.extend(ModuleMethods) if base.is_a?(Module)
105
113
  end
106
114
  end
107
115
 
@@ -12,6 +12,10 @@ Gem::Specification.new do |gem|
12
12
  gem.summary = %q{SmartProperties – Ruby accessors on steroids}
13
13
  gem.homepage = ""
14
14
 
15
+ gem.metadata = {
16
+ "source_code_uri" => "https://github.com/t6d/smart_properties"
17
+ }
18
+
15
19
  gem.files = `git ls-files`.split($\)
16
20
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
21
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
@@ -20,6 +24,6 @@ Gem::Specification.new do |gem|
20
24
  gem.version = SmartProperties::VERSION
21
25
 
22
26
  gem.add_development_dependency "rspec", "~> 3.0"
23
- gem.add_development_dependency "rake", "~> 10.0"
27
+ gem.add_development_dependency "rake", "~> 13.0"
24
28
  gem.add_development_dependency "pry"
25
29
  end
data/spec/base_spec.rb CHANGED
@@ -41,7 +41,7 @@ RSpec.describe SmartProperties do
41
41
  default_title = double(to_title: 'chunky')
42
42
 
43
43
  DummyClass.new do
44
- property :title, converts: :to_title, accepts: String, required: true, default: default_title
44
+ property :title, converts: :to_title, accepts: String, required: true, default: -> { default_title }
45
45
  end
46
46
  end
47
47
 
@@ -49,7 +49,7 @@ RSpec.describe SmartProperties do
49
49
 
50
50
  it "should return a property's configuration with #to_h" do
51
51
  expect(klass.properties.values.first.to_h).to match(
52
- accepter: String, converter: :to_title, default: an_instance_of(RSpec::Mocks::Double),
52
+ accepter: String, converter: :to_title, default: an_instance_of(Proc),
53
53
  instance_variable_name: :@title, name: :title, reader: :title, required: true
54
54
  )
55
55
  end
@@ -13,5 +13,38 @@ RSpec.describe SmartProperties, 'configuration error' do
13
13
 
14
14
  expect(&invalid_property_definition).to raise_error(SmartProperties::ConfigurationError, "SmartProperties do not support the following configuration options: invalid_option_1, invalid_option_2, invalid_option_3.")
15
15
  end
16
+
17
+ it "should accept default values that can't be mutated" do
18
+ valid_property_definition = lambda do
19
+ klass.class_eval do
20
+ property :proc, default: -> { }
21
+ property :numeric_float, default: 1.23
22
+ property :numeric_int, default: 456
23
+ property :string, default: "abc"
24
+ property :range, default: 123...456
25
+ property :bool_true, default: true
26
+ property :bool_false, default: false
27
+ property :nil, default: nil
28
+ property :symbol, default: :abc
29
+ property :module, default: Integer
30
+ end
31
+ end
32
+
33
+ expect(&valid_property_definition).not_to raise_error
34
+ end
35
+
36
+ it "should not accept default values that may be mutated" do
37
+ invalid_property_definition = lambda do
38
+ klass.class_eval do
39
+ property :title, default: []
40
+ end
41
+ end
42
+
43
+ expect(&invalid_property_definition).to(
44
+ raise_error(SmartProperties::ConfigurationError,
45
+ "Default attribute value [] cannot be specified as literal, "\
46
+ "use the syntax `default: -> { ... }` instead.")
47
+ )
48
+ end
16
49
  end
17
50
  end
@@ -48,4 +48,72 @@ RSpec.describe SmartProperties, 'default values' do
48
48
  end
49
49
  end
50
50
  end
51
+
52
+ context "when defining a new property with a literal default value" do
53
+ context 'with a numeric default' do
54
+ subject(:klass) { DummyClass.new { property :var, default: 123 } }
55
+
56
+ it 'accepts the default and returns it' do
57
+ instance = klass.new
58
+ expect(instance.var).to(be(123))
59
+ end
60
+ end
61
+
62
+ context 'with a string default' do
63
+ DEFAULT_VALUE = 'a string'
64
+ subject(:klass) { DummyClass.new { property :var, default: DEFAULT_VALUE } }
65
+
66
+ it 'accepts the default and returns it' do
67
+ instance = klass.new
68
+ expect(instance.var).to(eq(DEFAULT_VALUE))
69
+ end
70
+
71
+ it 'returns a copy of the string' do
72
+ instance = klass.new
73
+ expect(instance.var).to_not(be(DEFAULT_VALUE))
74
+ end
75
+
76
+ it 'mutating the instance variable does not mutate the original' do
77
+ instance = klass.new
78
+ instance.var[0] = 'o'
79
+ expect(DEFAULT_VALUE).to(eq('a string'))
80
+ end
81
+ end
82
+
83
+ context 'with a range default' do
84
+ subject(:klass) { DummyClass.new { property :var, default: 1..2 } }
85
+
86
+ it 'accepts the default and returns it' do
87
+ instance = klass.new
88
+ expect(instance.var).to(eq(1..2))
89
+ end
90
+ end
91
+
92
+ context 'with a true default' do
93
+ subject(:klass) { DummyClass.new { property :var, default: true } }
94
+
95
+ it 'accepts the default and returns it' do
96
+ instance = klass.new
97
+ expect(instance.var).to(be(true))
98
+ end
99
+ end
100
+
101
+ context 'with a false default' do
102
+ subject(:klass) { DummyClass.new { property :var, default: false } }
103
+
104
+ it 'accepts the default and returns it' do
105
+ instance = klass.new
106
+ expect(instance.var).to(be(false))
107
+ end
108
+ end
109
+
110
+ context 'with a symbol default' do
111
+ subject(:klass) { DummyClass.new { property :var, default: :foo } }
112
+
113
+ it 'accepts the default and returns it' do
114
+ instance = klass.new
115
+ expect(instance.var).to(be(:foo))
116
+ end
117
+ end
118
+ end
51
119
  end
@@ -225,4 +225,68 @@ RSpec.describe SmartProperties, 'intheritance' do
225
225
  end
226
226
  end
227
227
  end
228
+
229
+ context "through modules" do
230
+ let(:m) do
231
+ m = Module.new do
232
+ include SmartProperties
233
+ property :m, default: 1
234
+ end
235
+ end
236
+
237
+ let(:n) do
238
+ n = Module.new do
239
+ include SmartProperties
240
+ property :n, default: 2
241
+ end
242
+ end
243
+
244
+ it "is supported" do
245
+ n = self.n
246
+ m = self.m
247
+ o = Module.new {}
248
+
249
+ klass = Class.new do
250
+ include m
251
+ include o
252
+ include n
253
+ end
254
+
255
+ n.module_eval do
256
+ property :p, default: 3
257
+ end
258
+
259
+ instance = klass.new
260
+
261
+ expect(instance.m).to eq(1)
262
+ expect(instance.n).to eq(2)
263
+ expect(instance.p).to eq(3)
264
+
265
+ expect { klass.new(m: 4, n: 5, p: 6) }.to_not raise_error
266
+ end
267
+
268
+ it "yields properly ordered properties – child properties have higher precedence than parent properties" do
269
+ n = self.n
270
+ m = self.m
271
+
272
+ parent = Class.new do
273
+ include m
274
+ include n
275
+ end
276
+ expect(parent.new.m).to eq(1)
277
+
278
+ child = Class.new(parent) do
279
+ property :m, default: 0
280
+ end
281
+ expect(child.new.m).to eq(0)
282
+
283
+ grandchild = Class.new(child)
284
+ expect(grandchild.new.m).to eq(0)
285
+
286
+ grandgrandchild = Class.new(grandchild) do
287
+ property :m, default: 1000
288
+ end
289
+ expect(grandgrandchild.new.m).to eq(1000)
290
+ end
291
+ end
228
292
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_properties
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.0
4
+ version: 1.16.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Tennhard
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-13 00:00:00.000000000 Z
11
+ date: 2021-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -62,6 +62,7 @@ executables: []
62
62
  extensions: []
63
63
  extra_rdoc_files: []
64
64
  files:
65
+ - ".github/workflows/testing.yml"
65
66
  - ".gitignore"
66
67
  - ".ruby-version"
67
68
  - ".yardopts"
@@ -95,8 +96,9 @@ files:
95
96
  - spec/writable_spec.rb
96
97
  homepage: ''
97
98
  licenses: []
98
- metadata: {}
99
- post_install_message:
99
+ metadata:
100
+ source_code_uri: https://github.com/t6d/smart_properties
101
+ post_install_message:
100
102
  rdoc_options: []
101
103
  require_paths:
102
104
  - lib
@@ -111,8 +113,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
113
  - !ruby/object:Gem::Version
112
114
  version: '0'
113
115
  requirements: []
114
- rubygems_version: 3.0.3
115
- signing_key:
116
+ rubygems_version: 3.2.20
117
+ signing_key:
116
118
  specification_version: 4
117
119
  summary: SmartProperties – Ruby accessors on steroids
118
120
  test_files: