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.
@@ -22,7 +22,7 @@
22
22
  #
23
23
  module SmartProperties
24
24
 
25
- VERSION = "1.2.1"
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
- # Constructs a hash of attributes for a smart property enabled class using
137
- # the instructions provided by the block.
138
- #
139
- # @return [Hash<String, Object>] Hash of attributes
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
- # Returns the name of this singleton object.
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
- # Creates a new anonymous class that can act as an attribute builder.
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
- def to_hash
168
- Hash[each_pair.to_a]
169
- end
170
- end
137
+ def each(&block)
138
+ collection_with_parent_collection.each(&block)
171
139
  end
172
140
 
173
- private
141
+ protected
174
142
 
175
- def property_names_for(klass)
176
- klass.properties.keys
177
- end
143
+ attr_accessor :collection
178
144
 
179
- def store
180
- @store ||= {}
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 ? parent.properties.dup : {}
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
- attrs = attrs.merge(AttributeBuilder.build_attributes_for(self.class, &block)) if block
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
- self.class.properties.each do |_, property|
287
- value = attrs.key?(property.name) ? attrs.delete(property.name) : property.default(self)
288
- send(:"#{property.name}=", value)
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
 
@@ -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.0"
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
- its(:properties) { should have(1).property }
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
- its(:properties) { should have(1).property }
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
- its(:properties) { should have(2).property }
172
- its(:properties) { should have_key(:title) }
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
- its(:properties) { should have(2).property }
257
- its(:properties) { should have_key(:title) }
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, :subject)
488
+ c.send(:property, :priority)
493
489
  end
494
490
  end
495
491
 
496
- context "the class's properties" do
492
+ context "the class" do
497
493
 
498
- subject { klass.properties }
494
+ subject { klass }
499
495
 
500
- it { should have_key(:title) }
501
- it { should have_key(:subject) }
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 "should be initializable using a block", :pending => true do
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.1
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-18 00:00:00.000000000 Z
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.0'
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.0'
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: 183774927770524698
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: 183774927770524698
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: