smart_properties 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
19
+ .rspec
20
+ .rvmrc
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --protected
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in smart_properties.gemspec
4
+ gemspec
5
+
6
+ gem "ruby_gntp"
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Konstantin Tennhard
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # SmartProperties
2
+
3
+ Ruby accessors on steroids.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'smart_properties'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install smart_properties
18
+
19
+ ## Usage
20
+
21
+ `SmartProperties` are meant to extend standard Ruby classes. Simply include
22
+ the `SmartProperties` module and use the `property` method along with a name
23
+ and optional configuration parameters to define new properties. Calling this
24
+ method results in the generation of a getter and setter pair. In contrast to
25
+ traditional Ruby accessors -- created by calling `attr_accessor`,
26
+ `SmartProperties` provide much more functionality:
27
+
28
+ 1. input conversion,
29
+ 2. input validation,
30
+ 3. default values, and
31
+ 4. presence checking.
32
+
33
+ These features can be configured by calling the `property` method with
34
+ additional configuration parameters. The module also provides a default
35
+ implementation for a constructor that accepts a set of attributes. This is
36
+ comparable to the constructor of `ActiveRecord` objects.
37
+
38
+ Before we discuss the configuration of properties in more detail, we first
39
+ present a short synopsis of all the functionality provided by
40
+ `SmartProperties`.
41
+
42
+ ### Synopsis
43
+
44
+ The example below shows how to implement a class called `Message` which has
45
+ three properties: `subject`, `body`, and `priority`. The two properties,
46
+ `subject` and `priority`, are required whereas `body` is optional.
47
+ Furthermore, all properties use input conversion. The `priority` property also
48
+ uses validation and has a default value.
49
+
50
+ class Message
51
+ property :subject, :converts => :to_s
52
+
53
+ property :body, :converts => :to_s
54
+
55
+ property :priority, :converts => :to_sym,
56
+ :accepts => [:low, :normal, :high],
57
+ :default => :normal
58
+ :required => true
59
+ end
60
+
61
+ Creating an instance of this class without specifying any attributes will
62
+ result in an `ArgumentError` telling you to specify the required property
63
+ `subject`.
64
+
65
+ Message.new # => raises ArgumentError, "Message requires the property subject to be set"
66
+
67
+ Providing the constructor with a title but with an invalid value for the
68
+ property `priority` will also result in an `ArgumentError` telling you to
69
+ provide a proper value for the property `priority`.
70
+
71
+ m = Message.new :subject => 'Lorem ipsum'
72
+ m.priority # => :normal
73
+ m.priority = :urgent # => raises ArgumentError, Message does not accept :urgent as value for the property priority
74
+
75
+ Next, we discuss the various configuration options `SmartProperties` provide.
76
+
77
+ ### Property Configuration
78
+
79
+ This subsection explains the various configuration options `SmartProperties`
80
+ provide.
81
+
82
+ #### Input conversion
83
+
84
+ To automatically convert a given value for a property, you can use the
85
+ `:converts` configuration parameter. The parameter can either be a `Symbol` or
86
+ a `lambda` statement. Using a `Symbol` will instruct the setter to call the
87
+ method identified by this symbol on the object provided as input data and take
88
+ the result of this method call as value instead. The example below shows how
89
+ to implement a property that automatically converts all given input to a
90
+ `String` by calling `#to_s` on the object provided as input.
91
+
92
+ class Article
93
+ property :title, :converts => :to_s
94
+ end
95
+
96
+ If you need more fine-grained control, you can use a lambda statement to
97
+ specify how the conversion should be done. The statement will be evaluated in
98
+ the context of the class defining the property and takes the given value as
99
+ input. The example below shows how to implement a property that automatically
100
+ converts all given input to a slug representation.
101
+
102
+ class Article
103
+ property :slug, :converts => lambda { |slug| slug.downcase.gsub(/\s+/, '-').gsub(/\W/, '') }
104
+ end
105
+
106
+ #### Input validation
107
+
108
+ To ensure that a given value for a property is always of a certain type, you
109
+ can specify the `:accepts` configuration parameter. This will result in an
110
+ automatic validation whenever the setter for a certain property is called. The
111
+ example below shows how to implement a property which only accepts instances
112
+ of type `String` as input.
113
+
114
+ class Article
115
+ property :title, :accepts => String
116
+ end
117
+
118
+ Instead of using a class, you can also use a list of permitted values. The
119
+ example below shows how to implement a property that only accepts `true` or
120
+ `false` as values.
121
+
122
+ class Article
123
+ property :published, :accepts => [true, false]
124
+ end
125
+
126
+ You can also use a `lambda` statement for input validation if a more complex
127
+ validation procedure is required. The `lambda` statement is evaluated in the
128
+ context of the class that defines the property and receives the given value as
129
+ input. The example below shows how to implement a property called title that
130
+ only accepts values which match the given regular expression.
131
+
132
+ class Article
133
+ property :title, :accepts => lambda { |title| /^Lorem \w+$/ =~ title }
134
+ end
135
+
136
+ #### Default values
137
+
138
+ There is also support for default values. Simply use the `:default`
139
+ configuration parameter to configure a default value for a certain property.
140
+ The example below demonstrates how to implement a property that has 42 as
141
+ default value.
142
+
143
+ class Article
144
+ property :id, :default => 42
145
+ end
146
+
147
+ #### Presence checking
148
+
149
+ To ensure that a property is always set set and never `nil`, you can use the
150
+ `:required` configuration parameter. If present, this parameter will instruct
151
+ the setter of a property to not accept nil as input. The example below shows
152
+ how to implement a property that may not be `nil`.
153
+
154
+ class Article
155
+ property :title, :required => true
156
+ end
157
+
158
+ ## Contributing
159
+
160
+ 1. Fork it
161
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
162
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
163
+ 4. Push to the branch (`git push origin my-new-feature`)
164
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/setup'
3
+ require 'bundler/gem_tasks'
4
+ require 'yard'
5
+
6
+ YARD::Rake::YardocTask.new do |t|
7
+ t.files = ['lib/**/*.rb', 'README.md']
8
+ end
@@ -0,0 +1,217 @@
1
+ ##
2
+ # {SmartProperties} can be used to easily build more full-fledged accessors
3
+ # for standard Ruby classes. In contrast to regular accessors,
4
+ # {SmartProperties} support validation and conversion of input data, as well
5
+ # as, the specification of default values. Additionally, individual
6
+ # {SmartProperties} can be marked as required. This causes the runtime to
7
+ # throw an +ArgumentError+ whenever a required property has not been
8
+ # specified.
9
+ #
10
+ # In order to use {SmartProperties}, simply include the {SmartProperties}
11
+ # module and use the {ClassMethods#property} method to define properties.
12
+ #
13
+ # @see ClassMethods#property
14
+ # More information on how to configure properties
15
+ #
16
+ # @example Definition of a property that makes use of all {SmartProperties} features.
17
+ #
18
+ # property :language_code, :accepts => [:de, :en],
19
+ # :converts => :to_sym,
20
+ # :default => :de,
21
+ # :required => true
22
+ #
23
+ module SmartProperties
24
+
25
+ VERSION = "1.0.0"
26
+
27
+ class Property
28
+
29
+ attr_reader :name
30
+ attr_reader :default
31
+ attr_reader :converter
32
+ attr_reader :accepter
33
+
34
+ def initialize(name, attrs = {})
35
+ @name = name.to_sym
36
+ @default = attrs[:default]
37
+ @converter = attrs[:converts]
38
+ @accepter = attrs[:accepts]
39
+ @required = !!attrs[:required]
40
+ end
41
+
42
+ def required=(value)
43
+ @required = !!value
44
+ end
45
+
46
+ def required?
47
+ @required
48
+ end
49
+
50
+ def convert(value, scope)
51
+ return value unless converter
52
+
53
+ if converter.respond_to?(:call)
54
+ scope.instance_exec(value, &converter)
55
+ else
56
+ begin
57
+ value.send(:"#{converter}")
58
+ rescue NoMethodError
59
+ raise ArgumentError, "#{value.class.name} does not respond to ##{converter}"
60
+ end
61
+ end
62
+ end
63
+
64
+ def accepts?(value, scope)
65
+ return true unless value
66
+ return true unless accepter
67
+
68
+ if accepter.kind_of?(Enumerable)
69
+ accepter.include?(value)
70
+ elsif !accepter.kind_of?(Proc)
71
+ accepter === value
72
+ else
73
+ !!scope.instance_exec(value, &accepter)
74
+ end
75
+ end
76
+
77
+ def prepare(value, scope)
78
+ if required? && value.nil?
79
+ raise ArgumentError, "#{scope.class.name} requires the property #{self.name} to be set"
80
+ end
81
+
82
+ value = convert(value, scope) unless value.nil?
83
+
84
+ unless accepts?(value, scope)
85
+ raise ArgumentError, "#{scope.class.name} does not accept #{value.inspect} as value for the property #{self.name}"
86
+ end
87
+
88
+ @value = value
89
+ end
90
+
91
+ def define(klass)
92
+ property = self
93
+
94
+ scope = klass.instance_variable_get(:"@_smart_properties_method_scope") || begin
95
+ m = Module.new
96
+ klass.send(:include, m)
97
+ klass.instance_variable_set(:"@_smart_properties_method_scope", m)
98
+ m
99
+ end
100
+
101
+ scope.send(:attr_reader, name)
102
+ scope.send(:define_method, :"#{name}=") do |value|
103
+ instance_variable_set("@#{property.name}", property.prepare(value, self))
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ module ClassMethods
110
+
111
+ ##
112
+ # Returns the list of smart properties that for this class. This
113
+ # includes the properties than have been defined in the parent classes.
114
+ #
115
+ # @return [Array<Property>] The list of properties.
116
+ #
117
+ def properties
118
+ (@_smart_properties || {}).dup
119
+ end
120
+
121
+ ##
122
+ # Defines a new property from a name and a set of options. This results
123
+ # results in creating an accessor that has additional features:
124
+ #
125
+ # 1. Validation of input data by specifiying the +:accepts+ option:
126
+ # If you use a class as value for this option, the setter will check
127
+ # if the value it is about to assign is of this type. If you use an
128
+ # array, the setter will check if the value it is about to assign is
129
+ # included in this array. Finally, if you specify a block, it will
130
+ # invoke the block with the value it is about to assign and check if
131
+ # the block returns a thruthy value, meaning anything but +false+ and
132
+ # +nil+.
133
+ #
134
+ # 2. Conversion of input data by specifiying the +:converts+ option:
135
+ # If you use provide a symbol as value for this option, the setter will
136
+ # invoke this method on the object it is about to assign and take the
137
+ # result of this call instead. If you provide a block, it will invoke
138
+ # the block with the value it is about to assign and take the result
139
+ # of the block instead.
140
+ #
141
+ # 3. Providing a default value by specifiying the +:default+ option.
142
+ #
143
+ # 4. Forcing a property to be present by setting the +:required+ option
144
+ # to true.
145
+ #
146
+ #
147
+ # @param [Symbol] name the name of the property
148
+ #
149
+ # @param [Hash] options the list of options used to configure the property
150
+ # @option options [Array, Class, Proc] :accepts
151
+ # specifies how the validation is done
152
+ # @option options [Proc, Symbol] :converts
153
+ # specifies how the conversion is done
154
+ # @option options :default
155
+ # specifies the default value of the property
156
+ # @option options [true, false] :required
157
+ # specifies whether or not this property is required
158
+ #
159
+ # @return [Property] The defined property.
160
+ #
161
+ # @example Definition of a property that makes use of all {SmartProperties} features.
162
+ #
163
+ # property :language_code, :accepts => [:de, :en],
164
+ # :converts => :to_sym,
165
+ # :default => :de,
166
+ # :required => true
167
+ #
168
+ def property(name, options = {})
169
+ @_smart_properties ||= begin
170
+ parent = if self != SmartProperties
171
+ (ancestors[1..-1].find { |klass| klass.ancestors.include?(SmartProperties) && klass != SmartProperties })
172
+ end
173
+
174
+ parent ? parent.properties : {}
175
+ end
176
+
177
+ p = Property.new(name, options)
178
+ p.define(self)
179
+
180
+ @_smart_properties[name] = p
181
+ end
182
+ protected :property
183
+
184
+ end
185
+
186
+ class << self
187
+
188
+ private
189
+
190
+ ##
191
+ # Extends the class, which this module is included in, with a property
192
+ # method to define properties.
193
+ #
194
+ # @param [Class] base the class this module is included in
195
+ #
196
+ def included(base)
197
+ base.extend(ClassMethods)
198
+ end
199
+
200
+ end
201
+
202
+ ##
203
+ # Implements a key-value enabled constructor that acts as default
204
+ # constructor for all {SmartProperties}-enabled classes.
205
+ #
206
+ # @param [Hash] attrs the set of attributes that is used for initialization
207
+ #
208
+ def initialize(attrs = {})
209
+ attrs ||= {}
210
+
211
+ self.class.properties.each do |_, property|
212
+ value = attrs.key?(property.name) ? attrs.delete(property.name) : property.default
213
+ send(:"#{property.name}=", value)
214
+ end
215
+ end
216
+
217
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/smart_properties', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Konstantin Tennhard"]
6
+ gem.email = ["me@t6d.de"]
7
+ gem.description = <<-DESCRIPTION
8
+ SmartProperties are a more flexible and feature-rich alternative to
9
+ traditional Ruby accessors. They provide support for input conversion,
10
+ input validation, specifying default values and presence checking.
11
+ DESCRIPTION
12
+ gem.summary = %q{SmartProperties – Ruby accessors on steroids}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($\)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.name = "smart_properties"
19
+ gem.require_paths = ["lib"]
20
+ gem.version = SmartProperties::VERSION
21
+
22
+ gem.add_development_dependency "rspec", "~> 2.0"
23
+ gem.add_development_dependency "rake", "~> 0.8"
24
+ gem.add_development_dependency "yard", "~> 0.8"
25
+ gem.add_development_dependency "redcarpet", "~> 2.1"
26
+ gem.add_development_dependency "guard-rspec", "~> 0.7"
27
+ end
@@ -0,0 +1,325 @@
1
+ require 'spec_helper'
2
+
3
+ describe SmartProperties do
4
+
5
+ context "when extending an other class" do
6
+
7
+ subject do
8
+ Class.new.tap do |c|
9
+ c.send(:include, described_class)
10
+ end
11
+ end
12
+
13
+ it "should add a .property method" do
14
+ subject.should respond_to(:property)
15
+ end
16
+
17
+ end
18
+
19
+ context "when used to build a class that has a property called :title on a class" do
20
+
21
+ subject do
22
+ title = Object.new.tap do |o|
23
+ def o.to_title; 'chunky'; end
24
+ end
25
+
26
+ klass = Class.new.tap do |c|
27
+ c.send(:include, described_class)
28
+ c.instance_eval do
29
+ def name; "TestDummy"; end
30
+
31
+ property :title, :accepts => String,
32
+ :converts => :to_title,
33
+ :required => true,
34
+ :default => title
35
+ end
36
+ end
37
+
38
+ klass
39
+ end
40
+
41
+ context "instances of this class" do
42
+
43
+ klass = subject.call
44
+
45
+ subject do
46
+ klass.new
47
+ end
48
+
49
+ it { should respond_to(:title) }
50
+ it { should respond_to(:title=) }
51
+
52
+ it "should have 'chucky' as default value for title" do
53
+ subject.title.should be == 'chunky'
54
+ end
55
+
56
+ it "should convert all values that are assigned to title into strings" do
57
+ subject.title = double(:to_title => 'bacon')
58
+ subject.title.should be == 'bacon'
59
+ end
60
+
61
+ it "should not allow to set nil as title" do
62
+ expect { subject.title = nil }.to raise_error(ArgumentError, "TestDummy requires the property title to be set")
63
+ end
64
+
65
+ it "should not allow to set objects as title that do not respond to #to_title" do
66
+ expect { subject.title = Object.new }.to raise_error(ArgumentError, "Object does not respond to #to_title")
67
+ end
68
+
69
+ it "should not influence other instances that have been initialized with different attributes" do
70
+ other = klass.new :title => double(:to_title => 'Lorem ipsum')
71
+
72
+ subject.title.should be == 'chunky'
73
+ other.title.should be == 'Lorem ipsum'
74
+ end
75
+
76
+ end
77
+
78
+ context "when subclassed and extended with a property called text" do
79
+
80
+ superklass = subject.call
81
+
82
+ subject do
83
+ Class.new(superklass).tap do |c|
84
+ c.instance_eval do
85
+ property :text
86
+ end
87
+ end
88
+ end
89
+
90
+ context "instances of this subclass" do
91
+
92
+ klass = subject.call
93
+
94
+ subject do
95
+ klass.new
96
+ end
97
+
98
+ it { should respond_to(:title) }
99
+ it { should respond_to(:title=) }
100
+ it { should respond_to(:text) }
101
+ it { should respond_to(:text=) }
102
+
103
+ end
104
+
105
+ context "instances of the super class" do
106
+
107
+ subject do
108
+ superklass.new
109
+ end
110
+
111
+ it { should_not respond_to(:text) }
112
+ it { should_not respond_to(:text=) }
113
+
114
+ end
115
+
116
+ context "instances of this subclass that have been intialized from a set of attributes" do
117
+
118
+ klass = subject.call
119
+
120
+ subject do
121
+ klass.new :title => stub(:to_title => 'Message'), :text => "Hello"
122
+ end
123
+
124
+ it "should have the correct title" do
125
+ subject.title.should be == 'Message'
126
+ end
127
+
128
+ it "should have the correct text" do
129
+ subject.text.should be == 'Hello'
130
+ end
131
+
132
+ end
133
+
134
+ end
135
+
136
+ context "when extended with a :type property at runtime" do
137
+
138
+ klass = subject.call
139
+
140
+ subject do
141
+ klass.tap do |c|
142
+ c.instance_eval do
143
+ property :type, :converts => :to_sym
144
+ end
145
+ end
146
+ end
147
+
148
+ context "instances of this class" do
149
+
150
+ klass = subject.call
151
+
152
+ subject do
153
+ klass.new :title => double(:to_title => 'Lorem ipsum')
154
+ end
155
+
156
+ it { should respond_to(:type) }
157
+ it { should respond_to(:type=) }
158
+
159
+ end
160
+
161
+ context "when subclassing this class" do
162
+
163
+ superklass = subject.call
164
+
165
+ subject do
166
+ Class.new(superklass)
167
+ end
168
+
169
+ context "instances of this class" do
170
+
171
+ klass = subject.call
172
+
173
+ subject do
174
+ klass.new :title => double(:to_title => 'Lorem ipsum')
175
+ end
176
+
177
+ it { should respond_to :title }
178
+ it { should respond_to :title= }
179
+
180
+ it { should respond_to :type }
181
+ it { should respond_to :type= }
182
+
183
+ end
184
+
185
+ end
186
+
187
+ end
188
+
189
+ end
190
+
191
+ context "when used to build a class that has a property called :title which a lambda statement for conversion" do
192
+
193
+ subject do
194
+ Class.new.tap do |c|
195
+ c.send(:include, described_class)
196
+ c.instance_eval do
197
+ property :title, :converts => lambda { |t| "<title>#{t.to_s}</title>"}
198
+ end
199
+ end
200
+ end
201
+
202
+ context "instances of this class" do
203
+
204
+ klass = subject.call
205
+
206
+ subject do
207
+ klass.new
208
+ end
209
+
210
+ it "should convert the property title as specified the lambda statement" do
211
+ subject.title = "Lorem ipsum"
212
+ subject.title.should be == "<title>Lorem ipsum</title>"
213
+ end
214
+
215
+ end
216
+
217
+ end
218
+
219
+ context "when used to build a class that has a property called :visible which uses an array of valid values for acceptance checking" do
220
+
221
+ subject do
222
+ Class.new.tap do |c|
223
+ def c.name; "TestDummy"; end
224
+
225
+ c.send(:include, described_class)
226
+
227
+ c.instance_eval do
228
+ property :visible, :accepts => [true, false]
229
+ end
230
+ end
231
+ end
232
+
233
+ context "instances of this class" do
234
+
235
+ klass = subject.call
236
+
237
+ subject do
238
+ klass.new
239
+ end
240
+
241
+ it "should allow to set true as value for visible" do
242
+ expect { subject.visible = true }.to_not raise_error
243
+ end
244
+
245
+ it "should allow to set false as value for visible" do
246
+ expect { subject.visible = false }.to_not raise_error
247
+ end
248
+
249
+ it "should not allow to set :maybe as value for visible" do
250
+ expect { subject.visible = :maybe }.to raise_error(ArgumentError, "TestDummy does not accept :maybe as value for the property visible")
251
+ end
252
+
253
+ end
254
+
255
+ end
256
+
257
+ context 'when used to build a class that has a property called :license_plate which uses a lambda statement for accpetance checking' do
258
+
259
+ subject do
260
+ Class.new.tap do |c|
261
+ def c.name; 'TestDummy'; end
262
+
263
+ c.send(:include, described_class)
264
+
265
+ c.instance_eval do
266
+ property :license_plate, :accepts => lambda { |v| /\w{1,2} \w{1,2} \d{1,4}/.match(v) }
267
+ end
268
+ end
269
+ end
270
+
271
+ context 'instances of this class' do
272
+
273
+ klass = subject.call
274
+
275
+ subject do
276
+ klass.new
277
+ end
278
+
279
+ it 'should not a accept "invalid" as value for license_plate' do
280
+ expect { subject.license_plate = "invalid" }.to raise_error(ArgumentError, 'TestDummy does not accept "invalid" as value for the property license_plate')
281
+ end
282
+
283
+ it 'should accept "NE RD 1337" as license plate' do
284
+ expect { subject.license_plate = "NE RD 1337" }.to_not raise_error
285
+ end
286
+
287
+ end
288
+
289
+ end
290
+
291
+ context 'when used to build a class that has a property called :text whose getter is overriden' do
292
+
293
+ subject do
294
+ Class.new.tap do |c|
295
+ c.send(:include, described_class)
296
+
297
+ c.instance_eval do
298
+ property :text, :default => 'Hello'
299
+ end
300
+
301
+ c.class_eval do
302
+ def text
303
+ "<em>#{super}</em>"
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ context "instances of this class" do
310
+
311
+ klass = subject.call
312
+
313
+ subject do
314
+ klass.new
315
+ end
316
+
317
+ it "should return the accepted value for the property called :text" do
318
+ subject.text.should be == '<em>Hello</em>'
319
+ end
320
+
321
+ end
322
+
323
+ end
324
+
325
+ end
@@ -0,0 +1,10 @@
1
+ require 'bundler/setup'
2
+ require 'rspec'
3
+
4
+ require 'smart_properties'
5
+
6
+ Dir[File.join(File.dirname(__FILE__), 'support', '**', '*.rb')].each { |f| require f }
7
+
8
+ RSpec.configure do |config|
9
+
10
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smart_properties
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Konstantin Tennhard
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.8'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.8'
46
+ - !ruby/object:Gem::Dependency
47
+ name: yard
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '0.8'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.8'
62
+ - !ruby/object:Gem::Dependency
63
+ name: redcarpet
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '2.1'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '2.1'
78
+ - !ruby/object:Gem::Dependency
79
+ name: guard-rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '0.7'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '0.7'
94
+ description: ! " SmartProperties are a more flexible and feature-rich alternative
95
+ to\n traditional Ruby accessors. They provide support for input conversion,\n input
96
+ validation, specifying default values and presence checking.\n"
97
+ email:
98
+ - me@t6d.de
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - .gitignore
104
+ - .yardopts
105
+ - Gemfile
106
+ - Guardfile
107
+ - LICENSE
108
+ - README.md
109
+ - Rakefile
110
+ - lib/smart_properties.rb
111
+ - smart_properties.gemspec
112
+ - spec/smart_properties_spec.rb
113
+ - spec/spec_helper.rb
114
+ homepage: ''
115
+ licenses: []
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ segments:
127
+ - 0
128
+ hash: 2007724824068192771
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ segments:
136
+ - 0
137
+ hash: 2007724824068192771
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 1.8.24
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: SmartProperties – Ruby accessors on steroids
144
+ test_files:
145
+ - spec/smart_properties_spec.rb
146
+ - spec/spec_helper.rb
147
+ has_rdoc: