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 +4 -4
- data/.github/workflows/testing.yml +24 -0
- data/.ruby-version +1 -1
- data/README.md +1 -1
- data/lib/smart_properties/property.rb +20 -7
- data/lib/smart_properties/property_collection.rb +8 -7
- data/lib/smart_properties/version.rb +1 -1
- data/lib/smart_properties.rb +8 -0
- data/smart_properties.gemspec +5 -1
- data/spec/base_spec.rb +2 -2
- data/spec/configuration_error_spec.rb +33 -0
- data/spec/default_values_spec.rb +68 -0
- data/spec/inheritance_spec.rb +64 -0
- metadata +11 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d970dbdc63b4c78e01ec7f569bd00d9c37a61c672f97d084a5a01b205c80159
|
|
4
|
+
data.tar.gz: 0db3dca56f41fafc8c96cef4c2ebd54068ce505101a3f949549724522b88bf3f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
155
|
-
|
|
156
|
-
#
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
parent.properties.register(collection
|
|
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
|
|
79
|
+
@collection_with_parent_collection.merge!(parent_collection)
|
|
79
80
|
notify_children
|
|
80
81
|
nil
|
|
81
82
|
end
|
data/lib/smart_properties.rb
CHANGED
|
@@ -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
|
|
data/smart_properties.gemspec
CHANGED
|
@@ -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", "~>
|
|
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(
|
|
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
|
data/spec/default_values_spec.rb
CHANGED
|
@@ -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
|
data/spec/inheritance_spec.rb
CHANGED
|
@@ -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.
|
|
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:
|
|
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: '
|
|
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: '
|
|
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
|
-
|
|
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.
|
|
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:
|