smart_properties 1.2.1 → 1.2.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.
@@ -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: