smart_properties 1.2.1 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/smart_properties.rb +34 -58
- data/smart_properties.gemspec +1 -1
- data/spec/smart_properties_spec.rb +106 -29
- data/spec/support/smart_property_matcher.rb +46 -0
- metadata +8 -6
data/lib/smart_properties.rb
CHANGED
@@ -22,7 +22,7 @@
|
|
22
22
|
#
|
23
23
|
module SmartProperties
|
24
24
|
|
25
|
-
VERSION = "1.2.
|
25
|
+
VERSION = "1.2.2"
|
26
26
|
|
27
27
|
class Property
|
28
28
|
|
@@ -115,69 +115,35 @@ module SmartProperties
|
|
115
115
|
|
116
116
|
end
|
117
117
|
|
118
|
-
|
119
|
-
# {AttributeBuilder} is a singleton object that builds and keeps track of
|
120
|
-
# annonymous class that can be used to build attribute hashes for smart
|
121
|
-
# property enabled classes.
|
122
|
-
#
|
123
|
-
class << (AttributeBuilder = Object.new)
|
118
|
+
class PropertyCollection
|
124
119
|
|
125
|
-
|
126
|
-
# Returns an attribute builder for a given klass. If the attribute builder
|
127
|
-
# does not exist yet, it is created.
|
128
|
-
#
|
129
|
-
# @return [Class]
|
130
|
-
#
|
131
|
-
def [](klass)
|
132
|
-
store.fetch(klass) { new(property_names_for(klass)) }
|
133
|
-
end
|
120
|
+
include Enumerable
|
134
121
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
#
|
141
|
-
def build_attributes_for(klass, &block)
|
142
|
-
block.nil? ? {} : self[klass].new(&block).to_hash
|
122
|
+
attr_reader :parent
|
123
|
+
|
124
|
+
def initialize(parent = nil)
|
125
|
+
@parent = parent
|
126
|
+
@collection = {}
|
143
127
|
end
|
144
128
|
|
145
|
-
|
146
|
-
|
147
|
-
#
|
148
|
-
# @return String
|
149
|
-
#
|
150
|
-
def inspect
|
151
|
-
"AttributeBuilder"
|
129
|
+
def []=(name, value)
|
130
|
+
collection[name] = value
|
152
131
|
end
|
153
|
-
alias :to_s :inspect
|
154
132
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
# @return [Class]
|
159
|
-
#
|
160
|
-
def new(fields)
|
161
|
-
Struct.new(*fields) do
|
162
|
-
def initialize(*args, &block)
|
163
|
-
super
|
164
|
-
yield(self) if block
|
165
|
-
end
|
133
|
+
def [](name)
|
134
|
+
collection_with_parent_collection[name]
|
135
|
+
end
|
166
136
|
|
167
|
-
|
168
|
-
|
169
|
-
end
|
170
|
-
end
|
137
|
+
def each(&block)
|
138
|
+
collection_with_parent_collection.each(&block)
|
171
139
|
end
|
172
140
|
|
173
|
-
|
141
|
+
protected
|
174
142
|
|
175
|
-
|
176
|
-
klass.properties.keys
|
177
|
-
end
|
143
|
+
attr_accessor :collection
|
178
144
|
|
179
|
-
def
|
180
|
-
|
145
|
+
def collection_with_parent_collection
|
146
|
+
parent.nil? ? collection : parent.collection.merge(collection)
|
181
147
|
end
|
182
148
|
|
183
149
|
end
|
@@ -196,7 +162,7 @@ module SmartProperties
|
|
196
162
|
(ancestors[1..-1].find { |klass| klass.ancestors.include?(SmartProperties) && klass != SmartProperties })
|
197
163
|
end
|
198
164
|
|
199
|
-
parent ?
|
165
|
+
parent.nil? ? PropertyCollection.new : PropertyCollection.new(parent.properties)
|
200
166
|
end
|
201
167
|
end
|
202
168
|
|
@@ -281,11 +247,21 @@ module SmartProperties
|
|
281
247
|
#
|
282
248
|
def initialize(attrs = {}, &block)
|
283
249
|
attrs ||= {}
|
284
|
-
|
250
|
+
properties = self.class.properties.each.to_a
|
251
|
+
|
252
|
+
# Assign attributes or default values
|
253
|
+
properties.each do |_, property|
|
254
|
+
value = attrs.fetch(property.name, property.default(self))
|
255
|
+
send(:"#{property.name}=", value) if value
|
256
|
+
end
|
257
|
+
|
258
|
+
# Exectue configuration block
|
259
|
+
block.call(self) if block
|
285
260
|
|
286
|
-
|
287
|
-
|
288
|
-
|
261
|
+
# Check presence of all required properties
|
262
|
+
faulty_properties = properties.select { |_, property| property.required? && send(property.name).nil? }
|
263
|
+
unless faulty_properties.empty?
|
264
|
+
raise ArgumentError, "#{self.class.name} requires the following properties to be set: #{faulty_properties.map { |_, property| property.name }.sort.join(' ')}"
|
289
265
|
end
|
290
266
|
end
|
291
267
|
|
data/smart_properties.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
gem.version = SmartProperties::VERSION
|
21
21
|
|
22
|
-
gem.add_development_dependency "rspec", "~> 2.
|
22
|
+
gem.add_development_dependency "rspec", "~> 2.12"
|
23
23
|
gem.add_development_dependency "rake", "~> 0.8"
|
24
24
|
gem.add_development_dependency "guard-rspec", "~> 0.7"
|
25
25
|
end
|
@@ -62,8 +62,7 @@ describe SmartProperties do
|
|
62
62
|
klass
|
63
63
|
end
|
64
64
|
|
65
|
-
|
66
|
-
its(:properties) { should have_key(:title) }
|
65
|
+
it { should have_smart_property(:title) }
|
67
66
|
|
68
67
|
context "instances of this class" do
|
69
68
|
|
@@ -124,8 +123,7 @@ describe SmartProperties do
|
|
124
123
|
Class.new(superklass)
|
125
124
|
end
|
126
125
|
|
127
|
-
|
128
|
-
its(:properties) { should have_key(:title) }
|
126
|
+
it { should have_smart_property(:title) }
|
129
127
|
|
130
128
|
context "instances of this subclass" do
|
131
129
|
|
@@ -168,9 +166,8 @@ describe SmartProperties do
|
|
168
166
|
end
|
169
167
|
end
|
170
168
|
|
171
|
-
|
172
|
-
|
173
|
-
its(:properties) { should have_key(:text) }
|
169
|
+
it { should have_smart_property(:title) }
|
170
|
+
it { should have_smart_property(:text) }
|
174
171
|
|
175
172
|
context "instances of this subclass" do
|
176
173
|
|
@@ -253,9 +250,8 @@ describe SmartProperties do
|
|
253
250
|
end
|
254
251
|
end
|
255
252
|
|
256
|
-
|
257
|
-
|
258
|
-
its(:properties) { should have_key(:type) }
|
253
|
+
it { should have_smart_property(:title) }
|
254
|
+
it { should have_smart_property(:type) }
|
259
255
|
|
260
256
|
context "instances of this class" do
|
261
257
|
|
@@ -489,29 +485,16 @@ describe SmartProperties do
|
|
489
485
|
|
490
486
|
before do
|
491
487
|
klass.tap do |c|
|
492
|
-
c.send(:property, :
|
488
|
+
c.send(:property, :priority)
|
493
489
|
end
|
494
490
|
end
|
495
491
|
|
496
|
-
context "the class
|
492
|
+
context "the class" do
|
497
493
|
|
498
|
-
subject { klass
|
494
|
+
subject { klass }
|
499
495
|
|
500
|
-
it { should
|
501
|
-
it { should
|
502
|
-
|
503
|
-
end
|
504
|
-
|
505
|
-
context "the subclass's properties" do
|
506
|
-
|
507
|
-
subject { subklass.properties }
|
508
|
-
|
509
|
-
it { should have_key(:title) }
|
510
|
-
it { should have_key(:body) }
|
511
|
-
|
512
|
-
pending '(dynamically defined property is not inherited)' do
|
513
|
-
it { should have_key(:subject) }
|
514
|
-
end
|
496
|
+
it { should have_smart_property(:title) }
|
497
|
+
it { should have_smart_property(:priority) }
|
515
498
|
|
516
499
|
end
|
517
500
|
|
@@ -519,7 +502,11 @@ describe SmartProperties do
|
|
519
502
|
|
520
503
|
subject { subklass }
|
521
504
|
|
522
|
-
it
|
505
|
+
it { should have_smart_property(:title) }
|
506
|
+
it { should have_smart_property(:body) }
|
507
|
+
it { should have_smart_property(:priority) }
|
508
|
+
|
509
|
+
it "should be initializable using a block" do
|
523
510
|
configuration_instructions = lambda do |s|
|
524
511
|
s.title = "Lorem Ipsum"
|
525
512
|
s.priority = :low
|
@@ -543,4 +530,94 @@ describe SmartProperties do
|
|
543
530
|
|
544
531
|
end
|
545
532
|
|
533
|
+
context "when building a class that has a property which is not required and has a default" do
|
534
|
+
|
535
|
+
subject(:klass) do
|
536
|
+
Class.new.tap do |c|
|
537
|
+
c.send(:include, described_class)
|
538
|
+
c.send(:property, :title, :default => 'Lorem Ipsum')
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
context 'instances of that class' do
|
543
|
+
|
544
|
+
context 'when created with a set of attributes that explicitly contains nil for the title' do
|
545
|
+
|
546
|
+
subject(:instance) { klass.new :title => nil }
|
547
|
+
|
548
|
+
it "should have no title" do
|
549
|
+
instance.title.should be_nil
|
550
|
+
end
|
551
|
+
|
552
|
+
end
|
553
|
+
|
554
|
+
context 'when created without any arguments' do
|
555
|
+
|
556
|
+
subject(:instance) { klass.new }
|
557
|
+
|
558
|
+
it "should have the default title" do
|
559
|
+
instance.title.should be == 'Lorem Ipsum'
|
560
|
+
end
|
561
|
+
|
562
|
+
end
|
563
|
+
|
564
|
+
context 'when created with an empty block' do
|
565
|
+
|
566
|
+
subject(:instance) { klass.new {} }
|
567
|
+
|
568
|
+
it "should have the default title" do
|
569
|
+
instance.title.should be == 'Lorem Ipsum'
|
570
|
+
end
|
571
|
+
|
572
|
+
end
|
573
|
+
|
574
|
+
end
|
575
|
+
|
576
|
+
end
|
577
|
+
|
578
|
+
context "when building a class that has a property which is required and has no default" do
|
579
|
+
|
580
|
+
subject(:klass) do
|
581
|
+
Class.new.tap do |c|
|
582
|
+
c.send(:include, described_class)
|
583
|
+
c.send(:property, :title, :required => true)
|
584
|
+
|
585
|
+
def c.name; "Dummy"; end
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
context 'instances of that class' do
|
590
|
+
|
591
|
+
context 'when created with a set of attributes that explicitly contains nil for the title' do
|
592
|
+
|
593
|
+
subject(:instance) { klass.new :title => 'Lorem Ipsum' }
|
594
|
+
|
595
|
+
it "should have no title" do
|
596
|
+
instance.title.should be == 'Lorem Ipsum'
|
597
|
+
end
|
598
|
+
|
599
|
+
end
|
600
|
+
|
601
|
+
context 'when created with an block specifying that property' do
|
602
|
+
|
603
|
+
subject(:instance) { klass.new { |i| i.title = 'Lorem Ipsum' } }
|
604
|
+
|
605
|
+
it "should have the default title" do
|
606
|
+
instance.title.should be == 'Lorem Ipsum'
|
607
|
+
end
|
608
|
+
|
609
|
+
end
|
610
|
+
|
611
|
+
context "when created with no arguments" do
|
612
|
+
|
613
|
+
it "should raise an error stating that required properties are missing" do
|
614
|
+
expect { subject.new }.to raise_error(ArgumentError, "Dummy requires the following properties to be set: title")
|
615
|
+
end
|
616
|
+
|
617
|
+
end
|
618
|
+
|
619
|
+
end
|
620
|
+
|
621
|
+
end
|
622
|
+
|
546
623
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Matchers
|
2
|
+
class SmartPropertyMatcher
|
3
|
+
|
4
|
+
attr_reader :property_name
|
5
|
+
attr_reader :instance
|
6
|
+
|
7
|
+
def initialize(property_name)
|
8
|
+
@property_name = property_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(instance)
|
12
|
+
@instance = instance
|
13
|
+
smart_property_enabled? && is_smart_property?
|
14
|
+
end
|
15
|
+
|
16
|
+
def failure_message
|
17
|
+
return "expected #{instance.class.name} to have a property named #{property_name}" if smart_property_enabled?
|
18
|
+
return "expected #{instance.class.name} to be smart property enabled"
|
19
|
+
end
|
20
|
+
|
21
|
+
def negative_failure_message
|
22
|
+
"expected #{instance.class.name} to not have a property named #{property_name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def smart_property_enabled?
|
28
|
+
instance.ancestors.include?(::SmartProperties)
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_smart_property?
|
32
|
+
instance.properties[property_name].kind_of?(::SmartProperties::Property)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_smart_property(*args)
|
38
|
+
SmartPropertyMatcher.new(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
alias :have_smart_property :has_smart_property
|
42
|
+
end
|
43
|
+
|
44
|
+
RSpec.configure do |spec|
|
45
|
+
spec.include(Matchers)
|
46
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_properties
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '2.
|
21
|
+
version: '2.12'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '2.
|
29
|
+
version: '2.12'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: rake
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,7 @@ files:
|
|
80
80
|
- smart_properties.gemspec
|
81
81
|
- spec/smart_properties_spec.rb
|
82
82
|
- spec/spec_helper.rb
|
83
|
+
- spec/support/smart_property_matcher.rb
|
83
84
|
homepage: ''
|
84
85
|
licenses: []
|
85
86
|
post_install_message:
|
@@ -94,7 +95,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
95
|
version: '0'
|
95
96
|
segments:
|
96
97
|
- 0
|
97
|
-
hash:
|
98
|
+
hash: 1846347248225497114
|
98
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
100
|
none: false
|
100
101
|
requirements:
|
@@ -103,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
104
|
version: '0'
|
104
105
|
segments:
|
105
106
|
- 0
|
106
|
-
hash:
|
107
|
+
hash: 1846347248225497114
|
107
108
|
requirements: []
|
108
109
|
rubyforge_project:
|
109
110
|
rubygems_version: 1.8.24
|
@@ -113,4 +114,5 @@ summary: SmartProperties – Ruby accessors on steroids
|
|
113
114
|
test_files:
|
114
115
|
- spec/smart_properties_spec.rb
|
115
116
|
- spec/spec_helper.rb
|
117
|
+
- spec/support/smart_property_matcher.rb
|
116
118
|
has_rdoc:
|