activespec 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,75 @@
1
+ = ActiveSpec - Ruby Specifications Library
2
+
3
+ ActiveSpec is a Ruby implementation of the Specification pattern. It was born out of the desire to implement more complicated validations than the built-in Ruby On Rails validations macros allow and can be used with both Rails and non-Rails applications.
4
+
5
+ == Installation
6
+
7
+ ActiveSpec can currently be downloaded from Subversion. The repository URL is:
8
+
9
+ http://opensource.agileevolved.com/svn/root/activespec/trunk
10
+
11
+ It will be released as a gem shortly.
12
+
13
+ == Overview
14
+
15
+ ActiveSpec is comprised of four main components:
16
+
17
+ === 1. A set of low-level Specification classes
18
+ Many of these are designed to work with one or more attributes of a given object and are mostly modelled on the existing set of Ruby On Rails validation macros. There is also a CompositeSpecification class for working with more than one specification at one time. It is also very easy (and actively encouraged) to create your own low-level Specification classes. See ActiveSpec::Specifications for more information.
19
+
20
+ === 2. ActiveSpec::Base class
21
+ This can be sub-classed to create specifications in a more declarative manner. Here is a small example:
22
+
23
+ class UserSpecification < ActiveSpec::Base
24
+ requires_presence_of :username, :password
25
+ requires_confirmation_of :password
26
+ end
27
+
28
+ The main interface for any specification is a <tt>satisfied_by?</tt> method. The low-level specification classes (including CompositeSpecification) define <tt>satisfied_by?</tt> as an instance method and the ActiveSpec::Base specifications define it as a class method (it is not necessary to instantiate ActiveSpec::Base specifications).
29
+
30
+ === 3. High-level specification DSL
31
+ ActiveSpec provides a high level DSL for creating specifications, that builds on top of ActiveSpec::Base. An alternative way of creating the above UserSpecification using the specification DSL would be:
32
+
33
+ specification :user do
34
+ requires_presence_of :username, :password
35
+ requires_confirmation_of :password
36
+ end
37
+
38
+ This will automatically create a UserSpecification class with the given specifications. See ActiveSpec::Context for more information.
39
+
40
+ === 4. ActiveSpec::Satisfies module
41
+ The Satisfies module can be included into your own classes to provide functionality for working with specifications. This will allow you to directly associate specifications with your classes in a style similar to using Rails' validates_* macros.
42
+
43
+ class User
44
+ include ActiveSpec::Satisifies
45
+
46
+ must_satisfy :user_specification
47
+ end
48
+
49
+ user = User.new
50
+ user.satisfies_specs?
51
+
52
+ For more information, see ActiveSpec::Satisfies.
53
+
54
+ Also included with the library is a helper function for pre-loading specification classes in a given directory. See ActiveSpec#preload_specifications.
55
+
56
+ == Rails integration
57
+
58
+ You can use ActiveSpec in your Rails app with your ActiveRecord model classes by adding the following lines to your environment.rb file:
59
+
60
+ require 'active_spec'
61
+ ActiveRecord::Base.send(:include, ActiveSpec::Satisfies)
62
+
63
+ You will currently have to deal with the loading of your specification files yourself and you might find the preload_specifications method handy for this. There are plans to make integration with a Rails app even easier with an ActiveSpec Rails plugin in the near future.
64
+
65
+ == Future plans
66
+
67
+ One of the main features that ActiveSpec is missing that Rails' validations module provides is error message functionality. This will eventually be added to ActiveSpec, possibly with integration into the ActiveRecord::Errors class.
68
+
69
+ == Comments and feedback
70
+
71
+ Please send any comments to contact [AT] lukeredpath [DOT] co [DOT] uk. Please report any bugs on the Agile Evolved Open Source Trac at http://opensource.agileevolved.com.
72
+
73
+ == Credits
74
+
75
+ Copyright (c) Luke Redpath 2006. Released under the MIT license.
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+
4
+ module ActiveSpec
5
+
6
+ # Globs the given path for files with the name
7
+ # *_specification.rb and requires any files that it finds.
8
+ def self.preload_specifications(path_to_specs)
9
+ Dir.glob("#{path_to_specs}/*_specification.rb").each do |f|
10
+ require "#{path_to_specs}/#{File.basename(f, ".rb")}"
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ require File.dirname(__FILE__) + '/active_spec/specifications'
17
+ require File.dirname(__FILE__) + '/active_spec/base'
18
+ require File.dirname(__FILE__) + '/active_spec/satisfies'
19
+ require File.dirname(__FILE__) + '/active_spec/context'
20
+
21
+ include ActiveSpec::Context
@@ -0,0 +1,141 @@
1
+ # The ActiveSpec::Base class provides a more declarative way of
2
+ # creating specifications than using the low-level Specification
3
+ # classes. It essentially acts as a composite specification (it
4
+ # actually delegates to ActiveSpec::Specifications::CompositeSpecification
5
+ # when calling <tt>satisfied_by?</tt>) and allows you to create new specifications
6
+ # by sub-classing ActiveSpec::Base and calling the built-in macros to add
7
+ # individual specifications.
8
+ #
9
+ # This declarative way of creating specifications will be more familiar
10
+ # to those who are used to using Rails' built-in validates_* macros.
11
+ #
12
+ # Example specification:
13
+ #
14
+ # class UserSpecification < ActiveSpec::Base
15
+ # require_presence_of :username, :email
16
+ # require_confirmation_of :password
17
+ # require_inclusion_in 18..30, :age
18
+ # end
19
+ #
20
+ # You can execute an ActiveSpec against an object by calling
21
+ # the class method <tt>satisfied_by?</tt>. There is no need to
22
+ # instantiate an ActiveSpec class.
23
+ #
24
+ # user = User.new
25
+ # UserSpecification.satisfied_by?(user)
26
+ class ActiveSpec::Base
27
+
28
+ class << self
29
+
30
+ # Adds a ActiveSpec::Specifications::PresenceSpecification for the given attributes.
31
+ def requires_presence_of(*attributes)
32
+ add_specification ActiveSpec::Specifications::PresenceSpecification.new(*attributes)
33
+ end
34
+
35
+ # Adds a ActiveSpec::Specifications::CollectionSpecification for the given collection and attributes.
36
+ def requires_inclusion_in(collection, *attributes)
37
+ add_specification ActiveSpec::Specifications::CollectionSpecification.new(collection, *attributes)
38
+ end
39
+
40
+ # Adds a ActiveSpec::Specifications::CollectionSpecification wrapped in a
41
+ # ActiveSpec::Specifications::NotSpecification decorator
42
+ # for the given collection and attributes.
43
+ def requires_exclusion_from(collection, *attributes)
44
+ add_specification ActiveSpec::Specifications::NotSpecification.new(
45
+ ActiveSpec::Specifications::CollectionSpecification.new(collection, *attributes)
46
+ )
47
+ end
48
+
49
+ # Adds a ActiveSpec::Specifications::ConfirmationSpecification for the given attributes.
50
+ def requires_confirmation_of(*attributes)
51
+ add_specification ActiveSpec::Specifications::ConfirmationSpecification.new(*attributes)
52
+ end
53
+
54
+ # Adds a ActiveSpec::Specifications::SizeSpecification for the given attributes.
55
+ def requires_size(size, *attributes)
56
+ add_specification ActiveSpec::Specifications::SizeSpecification.new(size, *attributes)
57
+ end
58
+
59
+ # Adds a ActiveSpec::Specifications::MatchSpecification for the given regexp and attributes.
60
+ def requires_match(regexp, *attributes)
61
+ # TODO add_specification MatchSpecification.new(regexp, *attributes)
62
+ end
63
+
64
+ # A generic method that lets you add arbitrary specifications in several
65
+ # formats. <tt>specification</tt> can be a specification class (that
66
+ # responds to <tt>satisfied_by?</tt>), a Symbol (which is used to find a specification
67
+ # class) or a Proc object. As an alternative to passing a Proc, you can
68
+ # simply call <tt>satisfy</tt> with a block. The Proc/block will
69
+ # be passed the object that is sent to <tt>satisfied_by?</tt> when <tt>satisfied_by?</tt>
70
+ # is called.
71
+ #
72
+ # # Using a class
73
+ # must_satisfy UserSpecification
74
+ #
75
+ # # Using a symbol
76
+ # must_satisfy :user_specification
77
+ #
78
+ # # Using a Proc
79
+ # must_satisfy proc{ |object| # do something with object }
80
+ #
81
+ # # Passing in a block
82
+ # must_satisfy { |object| # do something with object }
83
+ #
84
+ # This method gives you the power to create very complicated specifications.
85
+ # Because you can pass in anything that responds to <tt>satisfied_by?</tt>, you can
86
+ # pass in your own low-level specification classes, ActiveSpec::Specifications::CompositeSpecification
87
+ # classes, or even other ActiveSpec classes, e.g.
88
+ #
89
+ # class UserSpecification < ActiveSpec::Base
90
+ # require_presence_of :email
91
+ # end
92
+ #
93
+ # class AdvancedUserSpecification < ActiveSpec::Base
94
+ # must_satisfy :user_specification
95
+ # require_presence_of :something_else
96
+ # end
97
+ def must_satisfy(specification=nil, &block)
98
+ case specification
99
+ when Symbol
100
+ add_specification Object.const_get(Inflector::classify(specification))
101
+ when Proc
102
+ add_specification ActiveSpec::Specifications::ProcSpecification.new(specification)
103
+ when Class
104
+ add_specification specification
105
+ else
106
+ if block_given?
107
+ add_specification ActiveSpec::Specifications::ProcSpecification.new(block)
108
+ else
109
+ raise "satisfy must be passed a symbol, proc, class or block"
110
+ end
111
+ end
112
+ end
113
+
114
+ # Calls <tt>satisfied_by?</tt> on each specification with
115
+ # the given object. Returns <tt>true</tt> if all of the specifications
116
+ # pass, otherwise it returns <tt>false</tt>.
117
+ def satisfied_by?(object)
118
+ composite_spec.satisfied_by?(object)
119
+ end
120
+
121
+ protected
122
+ def write_inheritable_set(key, spec)
123
+ existing_specs = read_inheritable_attribute(key) || []
124
+ specs_to_write = existing_specs << spec
125
+ write_inheritable_attribute(key, specs_to_write)
126
+ end
127
+
128
+ def add_specification(spec)
129
+ write_inheritable_set(:specifications, spec)
130
+ end
131
+
132
+ # Returns a ActiveSpec::Specifications::CompositeSpecification with all of the
133
+ # declared specifications.
134
+ def composite_spec
135
+ composite_spec = ActiveSpec::Specifications::CompositeSpecification.new
136
+ read_inheritable_attribute(:specifications).each { |spec| composite_spec.add_specification(spec) }
137
+ composite_spec
138
+ end
139
+ end
140
+
141
+ end
@@ -0,0 +1,30 @@
1
+ # Provides a high-level DSL-like interface to the ActiveSpec
2
+ # library.
3
+ module ActiveSpec::Context
4
+
5
+ # This method allows you to build ActiveSpec specifications
6
+ # using a DSL-like syntax. Pass in a name for your specification
7
+ # (without the _specification suffix which will be added
8
+ # automatically) and a block which contains your individual
9
+ # specifications.
10
+ #
11
+ # The ActiveSpec::Context module gets included automatically
12
+ # when the ActiveSpec library is required, so the <tt>specification</tt>
13
+ # method is available in the global namespace.
14
+ #
15
+ # Example usage:
16
+ #
17
+ # specification :user do
18
+ # should_require_presence_of :username, :password
19
+ # should_require_confirmation_of :password
20
+ # end
21
+ #
22
+ # The above code will create a UserSpecification class (a sub-class
23
+ # of ActiveSpec::Base) with the given specifications.
24
+ def specification(name, &block)
25
+ klass = Inflector::classify("#{name.to_s}_specification")
26
+ Object.const_set(klass, Class.new(ActiveSpec::Base))
27
+ Object.const_get(klass).class_eval(&block)
28
+ end
29
+
30
+ end
@@ -0,0 +1,78 @@
1
+ # Provides functionality for working with specifications
2
+ # inside your own classes. Simply include the module
3
+ # inside any class that you want to use with specifications.
4
+ #
5
+ # Example usage:
6
+ #
7
+ # specification :user do
8
+ # should_require_presence_of :username
9
+ # end
10
+ #
11
+ # specification :authenticated_user do
12
+ # # some other specs
13
+ # end
14
+ #
15
+ # class User
16
+ # must_satisfy :user_specification
17
+ # must_satisfy :authenticated_user_specification, :if => { |u| u.has_password? }
18
+ # end
19
+ #
20
+ # user = User.new
21
+ # user.satisfies_specs? # returns false
22
+ # user.username = 'joebloggs'
23
+ # user.satisfies_specs? # returns true
24
+ #
25
+ # For information on adding specifications, see the ActiveSpec::Satisfies::ClassMethods module.
26
+ module ActiveSpec::Satisfies
27
+
28
+ def self.included(base)
29
+ base.extend(ClassMethods)
30
+ end
31
+
32
+ module ClassMethods
33
+
34
+ # Specifies a specification class (can be anything that responds to <tt>satisfied_by?</tt>)
35
+ # that any instances of the class should satisfy. Takes one option:
36
+ #
37
+ # * <tt>:if</tt> - used to pass in a block that must return true
38
+ # for the specification to apply. Can be used to conditionally declare
39
+ # specifications.
40
+ def must_satisfy(spec_name, opts={})
41
+ satisfies = read_inheritable_attribute(:satisfies) || []
42
+ satisfies << {:spec => spec_name, :options => opts}
43
+ write_inheritable_attribute(:satisfies, satisfies)
44
+ end
45
+
46
+ end
47
+
48
+ # Checks that all relevant, assigned specifications pass.
49
+ # Specifications assigned with the <tt>:if</tt> option will only
50
+ # be evaluated if the <tt>:if</tt> block returns true.
51
+ def satisfies_specs?
52
+ satisfies = self.class.read_inheritable_attribute(:satisfies) || []
53
+ satisfies.collect do |s|
54
+ if s[:options][:if]
55
+ return true unless passes_predicate?(s[:options][:if])
56
+ end
57
+ satisfies?(s[:spec])
58
+ end.grep(false).empty?
59
+ end
60
+
61
+ protected
62
+ def satisfies?(spec)
63
+ if spec.instance_of?(Symbol)
64
+ spec = Kernel.const_get(Inflector::classify(spec))
65
+ end
66
+ spec.satisfied_by? self
67
+ end
68
+
69
+ def passes_predicate?(predicate)
70
+ case predicate
71
+ when Symbol
72
+ return self.send(predicate)
73
+ when Proc
74
+ return predicate.call(self)
75
+ end
76
+ end
77
+
78
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveSpec
2
+ module Specifications
3
+ end
4
+ end
5
+
6
+ require File.dirname(__FILE__) + '/specifications/composite_specification'
7
+ require File.dirname(__FILE__) + '/specifications/attributes_specification'
8
+ require File.dirname(__FILE__) + '/specifications/presence_specification'
9
+ require File.dirname(__FILE__) + '/specifications/confirmation_specification'
10
+ require File.dirname(__FILE__) + '/specifications/size_specification'
11
+ require File.dirname(__FILE__) + '/specifications/collection_specification'
12
+ require File.dirname(__FILE__) + '/specifications/proc_specification'
13
+ require File.dirname(__FILE__) + '/specifications/not_specification'
@@ -0,0 +1,39 @@
1
+ # This is the base class for all specification classes that
2
+ # can act on any number of public object attributes. If you want
3
+ # to write a specification class that can act on many public
4
+ # object attributes, sub-class this class and define your own
5
+ # <tt>satisfied_by?</tt> method.
6
+ class ActiveSpec::Specifications::AttributesSpecification
7
+
8
+ # The default initialize method stores all attributes in
9
+ # an instance variable called <tt>@attributes</tt>. When
10
+ # sub-classing, it is important to call <tt>super</tt>
11
+ # with the given attributes, for example:
12
+ #
13
+ # class MyOwnSpecification < ActiveSpec::Specifications::AttributesSpecification
14
+ # def initialize(custom_var, *attributes)
15
+ # # do something with custom_var
16
+ # super(*attributes)
17
+ # end
18
+ # end
19
+ def initialize(*attributes)
20
+ @attributes = attributes
21
+ end
22
+
23
+ # This default implementation always returns <tt>true</tt>.
24
+ # Override this method in your own specification classes.
25
+ def satisfied_by?(object)
26
+ true
27
+ end
28
+
29
+ protected
30
+
31
+ # Yields each attribute in the <tt>@attributes</tt>
32
+ # collection to the given block and if all block
33
+ # calls return <tt>true</tt>, then it returns <tt>true</tt>.
34
+ # Otherwise it returns <tt>false</tt>.
35
+ def attributes_satisfy?(&block) #:yields: attribute
36
+ @attributes.collect { |a| yield a }.grep(false).empty?
37
+ end
38
+
39
+ end
@@ -0,0 +1,42 @@
1
+ # Checks the values of one or more attributes for
2
+ # inclusion within a given collection. This is similar to
3
+ # the Rails validation macro <tt>validates_inclusion_of</tt>.
4
+ #
5
+ # Example usage:
6
+ #
7
+ # include ActiveSpec::Specifications
8
+ #
9
+ # class ColorPicker
10
+ # def color
11
+ # 'red'
12
+ # end
13
+ # end
14
+ #
15
+ # spec_one = CollectionSpecification.new(['red', 'yellow'], :color)
16
+ # spec_two = CollectionSpecification.new(['green', 'yellow'], :color)
17
+ # spec_one.satisfied_by?(ColorPicker.new) # returns true
18
+ # spec_two.satisfied_by?(ColorPicker.new) # returns false
19
+ #
20
+ # The equivalent of the Rails <tt>validates_exclusion_of</tt>
21
+ # validation macro can be implemented by wrapping a CollectionSpecification
22
+ # inside a NotSpecification decorator.
23
+ class ActiveSpec::Specifications::CollectionSpecification < ActiveSpec::Specifications::AttributesSpecification
24
+
25
+ # <tt>collection</tt> can be any object that responds to
26
+ # <tt>include?</tt>, such as Array and Range objects.
27
+ def initialize(collection, *attributes)
28
+ @collection = collection
29
+ super(*attributes)
30
+ end
31
+
32
+ # Returns false if the object fails to respond to
33
+ # to any of the given attributes, or if any of the attribute
34
+ # values for the object are not in the specified collection.
35
+ def satisfied_by?(object)
36
+ attributes_satisfy? do |a|
37
+ return false unless object.respond_to?(a)
38
+ return false unless @collection.include?(object.send(a))
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,24 @@
1
+ # Uses the Composite pattern to allow an object to be
2
+ # run against any number of specifications in one operation.
3
+ # A specification can be any object that responds to <tt>satisfied_by?</tt>.
4
+ # Because the composite specification also responds to <tt>satisfied_by?</tt>, you can
5
+ # add composite specifications to other composite specifications.
6
+ class ActiveSpec::Specifications::CompositeSpecification
7
+
8
+ def initialize
9
+ @specs = []
10
+ end
11
+
12
+ # Adds a new specification to the composite
13
+ def add_specification(spec)
14
+ @specs << spec
15
+ end
16
+
17
+ # Calls <tt>satisfied_by?</tt> on each specification with
18
+ # the given object. Returns <tt>true</tt> if all of the specifications
19
+ # pass, otherwise it returns <tt>false</tt>.
20
+ def satisfied_by?(object)
21
+ @specs.collect { |s| s.satisfied_by?(object) }.grep(false).empty?
22
+ end
23
+
24
+ end
@@ -0,0 +1,50 @@
1
+ # Looks for a confirmation attribute for each given attribute.
2
+ # It looks for a confirmation attribtue named attributename_confirmation
3
+ # and checks that the value of the attribute and its confirmation attribute match.
4
+ # This could be used to validate password confirmations.
5
+ #
6
+ # Example usage:
7
+ #
8
+ # include ActiveSpec::Specifications
9
+ #
10
+ # class User
11
+ # attr_accessor :password, :password_confirmation
12
+ # end
13
+ #
14
+ # spec = ConfirmationSpecification.new(:password)
15
+ #
16
+ # # passing spec
17
+ # user_one = User.new
18
+ # user_one.password = 'foo'
19
+ # user_one.password_confirmation = 'foo'
20
+ # spec.satisfied_by?(user_one) # returns true
21
+ #
22
+ # # failing spec
23
+ # user_two = User.new
24
+ # user_two.password = 'foo'
25
+ # user_two.password_confirmation = 'bar'
26
+ # spec.satisfied_by?(user_two) # returns false
27
+ #
28
+ # This works in the same way as the Rails validation macro
29
+ # <tt>validates_confirmation_of</tt>.
30
+ class ActiveSpec::Specifications::ConfirmationSpecification < ActiveSpec::Specifications::AttributesSpecification
31
+
32
+ # Returns false if the given object fails to respond to
33
+ # any of the attributes, or the attributes' confirmation
34
+ # methods. If both attribute and attribute confirmation methods
35
+ # respond, then it checks that the values of both match.
36
+ def satisfied_by?(object)
37
+ attributes_satisfy? do |a|
38
+ return false unless object.respond_to?(a)
39
+ return false unless object.respond_to?(confirmation_attribute(a))
40
+ return false unless object.send(a) == object.send(confirmation_attribute(a))
41
+ end
42
+ end
43
+
44
+ protected
45
+
46
+ def confirmation_attribute(attribute)
47
+ "#{attribute.to_s}_confirmation"
48
+ end
49
+
50
+ end
@@ -0,0 +1,19 @@
1
+ # A simple decorator class that negates the
2
+ # specification that it wraps, e.g.:
3
+ #
4
+ # include ActiveSpec::Specifications
5
+ #
6
+ # spec = AlwaysPassSpec.new(:foo)
7
+ # spec.satisfied_by? # returns true
8
+ # NotSpecification.new(spec).satisfied_by? # returns false
9
+ class ActiveSpec::Specifications::NotSpecification
10
+
11
+ def initialize(decorated_spec)
12
+ @decorated_spec = decorated_spec
13
+ end
14
+
15
+ def satisfied_by?(object)
16
+ not @decorated_spec.satisfied_by?(object)
17
+ end
18
+
19
+ end
@@ -0,0 +1,35 @@
1
+ # This specification will check that the values for
2
+ # each attribute are neither nil, or empty.
3
+ #
4
+ # Example usage:
5
+ #
6
+ # include ActiveSpec::Specifications
7
+ #
8
+ # class Foo
9
+ # attr_accessor :bar
10
+ # end
11
+ #
12
+ # spec = PresenceSpecification.new(:bar)
13
+ # foo = Foo.new
14
+ # spec.satisfied_by?(foo) # returns false
15
+ # foo.bar = ''
16
+ # spec.satisfied_by?(foo) # returns false
17
+ # foo.bar = 'baz'
18
+ # spec.satisfied_by?(foo) # returns true
19
+ #
20
+ # This works in the same way as the Rails validation
21
+ # macro <tt>validates_presence_of</tt>.
22
+ class ActiveSpec::Specifications::PresenceSpecification < ActiveSpec::Specifications::AttributesSpecification
23
+
24
+ # Checks that the value for each attribute on the given object
25
+ # is neither nil or empty. It will also return false if
26
+ # the object fails to respond to any attribute.
27
+ def satisfied_by?(object)
28
+ attributes_satisfy? do |a|
29
+ return false unless object.respond_to?(a)
30
+ return false if object.send(a).nil?
31
+ return false if object.send(a).empty?
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,22 @@
1
+ # A ProcSpecification is a simple wrapper around a Proc
2
+ # object, that makes it conform to the Specification
3
+ # interface (with a <tt>satisfied_by?</tt> method). If the given
4
+ # Proc returns true, the specification passes, otherwise
5
+ # it does not pass.
6
+ class ActiveSpec::Specifications::ProcSpecification
7
+
8
+ def initialize(proc)
9
+ @proc = proc
10
+ end
11
+
12
+ # Calls the wrapped Proc object, passing the given object
13
+ # to the proc. Returns the result of the Proc (either <tt>true</tt>
14
+ # or <tt>false</tt>). If the Proc raises an exception of any
15
+ # kind, then <tt>satisfied_by?</tt> will return <tt>false</tt>.
16
+ def satisfied_by?(object)
17
+ @proc.call(object)
18
+ rescue
19
+ false
20
+ end
21
+
22
+ end
@@ -0,0 +1,38 @@
1
+ # Checks that the values for the given attributes are
2
+ # of a particular size. The size can be a precise number (Fixnum)
3
+ # or a Range. If a Range is given, the specification will pass
4
+ # if the value's size falls within the Range.
5
+ #
6
+ # Creating a SizeSpecification with a Fixnum is the same as
7
+ # the Rails <tt>validates_size_of</tt> macro with the <tt>:exactly</tt> option.
8
+ # Using a Range is the equivalent of using <tt>validates_size_of</tt>
9
+ # with the <tt>:in</tt> option.
10
+ class ActiveSpec::Specifications::SizeSpecification < ActiveSpec::Specifications::AttributesSpecification
11
+
12
+ # <tt>size</tt> can be a Fixnum or a Range.
13
+ def initialize(size, *attributes)
14
+ @size = size
15
+ super(*attributes)
16
+ end
17
+
18
+ # Will return false if the object does not respond
19
+ # to any of the specified attributes, or if any of the
20
+ # the values for each attribute does not respond to
21
+ # <tt>size</tt>. Otherwise it will check that the size of the value
22
+ # for each attribute equals the given size (if size is
23
+ # a Fixnum) or within the given Range.
24
+ def satisfied_by?(object)
25
+ attributes_satisfy? do |a|
26
+ return false unless object.respond_to?(a)
27
+ return false unless object.send(a).respond_to?(:size)
28
+ case @size
29
+ when Fixnum
30
+ return false unless object.send(a).size == @size
31
+ when Range
32
+ return false unless @size.include?(object.send(a).size)
33
+ end
34
+ true
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,56 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ include ActiveSpec::Specifications
3
+
4
+ context "Collection specification" do
5
+ setup do
6
+ @spec = CollectionSpecification.new(['foo', 'bar'], :widget)
7
+ end
8
+
9
+ specify "should pass if the given object returns a value in the specified collection for the specified attribute" do
10
+ klass = Class.new
11
+ klass.send(:attr_accessor, :widget)
12
+ object = klass.new
13
+ object.widget = 'foo'
14
+ @spec.should_be_satisfied_by object
15
+ object.widget = 'bar'
16
+ @spec.should_be_satisfied_by object
17
+ end
18
+
19
+ specify "should not pass if the given object doesn't respond to the specified attribute" do
20
+ @spec.should_not_be_satisfied_by Object.new
21
+ end
22
+
23
+ specify "should not pass if the given object doesn't return a value in the specified collection for the specified attribute" do
24
+ klass = Class.new
25
+ klass.send(:attr_accessor, :widget)
26
+ object = klass.new
27
+ object.widget = 'baz'
28
+ @spec.should_not_be_satisfied_by object
29
+ end
30
+ end
31
+
32
+ context "Collection specification with range" do
33
+ setup do
34
+ @spec = CollectionSpecification.new(Range.new(10, 30), :age)
35
+ end
36
+
37
+ specify "should pass if the given object returns a fixnum for the attribute in the given range" do
38
+ klass = Class.new
39
+ klass.send(:attr_accessor, :age)
40
+ object = klass.new
41
+ object.age = 20
42
+ @spec.should_be_satisfied_by object
43
+ end
44
+
45
+ specify "should not pass if the given object does not respond to the specified attribute" do
46
+ @spec.should_not_be_satisfied_by Object.new
47
+ end
48
+
49
+ specify "should not pass if the given object returns a fixnum for the attribute that isn't in the given range" do
50
+ klass = Class.new
51
+ klass.send(:attr_accessor, :age)
52
+ object = klass.new
53
+ object.age = 35
54
+ @spec.should_not_be_satisfied_by object
55
+ end
56
+ end
@@ -0,0 +1,66 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ include ActiveSpec::Specifications
3
+
4
+ context "Empty composite specifcation" do
5
+ setup do
6
+ @spec = CompositeSpecification.new
7
+ end
8
+
9
+ specify "should pass" do
10
+ @spec.should_be_satisfied_by Object.new
11
+ end
12
+ end
13
+
14
+ context "Composite specifcation with just passing specifications" do
15
+ setup do
16
+ @spec = CompositeSpecification.new
17
+ @spec.add_specification(PresenceSpecification.new(:foo))
18
+ @spec.add_specification(PresenceSpecification.new(:bar))
19
+ klass = Class.new
20
+ klass.send(:attr_accessor, :foo)
21
+ klass.send(:attr_accessor, :bar)
22
+ @object = klass.new
23
+ @object.foo = 'something'
24
+ @object.bar = 'something else'
25
+ end
26
+
27
+ specify "should pass" do
28
+ @spec.should_be_satisfied_by @object
29
+ end
30
+ end
31
+
32
+ context "Composite specifcation with just failing specifications" do
33
+ setup do
34
+ @spec = CompositeSpecification.new
35
+ @spec.add_specification(PresenceSpecification.new(:foo))
36
+ @spec.add_specification(PresenceSpecification.new(:bar))
37
+ klass = Class.new
38
+ klass.send(:attr_accessor, :foo)
39
+ klass.send(:attr_accessor, :bar)
40
+ @object = klass.new
41
+ @object.foo = ''
42
+ @object.bar = nil
43
+ end
44
+
45
+ specify "should not pass" do
46
+ @spec.should_not_be_satisfied_by @object
47
+ end
48
+ end
49
+
50
+ context "Composite specifcation with passing and failing specifications" do
51
+ setup do
52
+ @spec = CompositeSpecification.new
53
+ @spec.add_specification(PresenceSpecification.new(:foo))
54
+ @spec.add_specification(PresenceSpecification.new(:bar))
55
+ klass = Class.new
56
+ klass.send(:attr_accessor, :foo)
57
+ klass.send(:attr_accessor, :bar)
58
+ @object = klass.new
59
+ @object.foo = 'something'
60
+ @object.bar = nil
61
+ end
62
+
63
+ specify "should not pass" do
64
+ @spec.should_not_be_satisfied_by @object
65
+ end
66
+ end
@@ -0,0 +1,39 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ include ActiveSpec::Specifications
3
+
4
+ context "Confirmation specification" do
5
+ setup do
6
+ @spec = ConfirmationSpecification.new(:password)
7
+ end
8
+
9
+ specify "should pass if the given object returns the same value for attribute as it does for attribute_confirmation" do
10
+ klass = Class.new
11
+ klass.send(:attr_accessor, :password)
12
+ klass.send(:attr_accessor, :password_confirmation)
13
+ object = klass.new
14
+ object.password = 'foobar'
15
+ object.password_confirmation = 'foobar'
16
+ @spec.should_be_satisfied_by object
17
+ end
18
+
19
+ specify "should fail if the given object doesn't respond to the specified attribute" do
20
+ @spec.should_not_be_satisfied_by Object.new
21
+ end
22
+
23
+ specify "should fail if the given object doesn't respond to specified_attribute_confirmation" do
24
+ klass = Class.new
25
+ klass.send(:attr_accessor, :password)
26
+ object = klass.new
27
+ @spec.should_not_be_satisfied_by object
28
+ end
29
+
30
+ specify "should fail if the given object returns a different value for attribute as it does for attribute_confirmation" do
31
+ klass = Class.new
32
+ klass.send(:attr_accessor, :password)
33
+ klass.send(:attr_accessor, :password_confirmation)
34
+ object = klass.new
35
+ object.password = 'foobar'
36
+ object.password_confirmation = 'foobarbaz'
37
+ @spec.should_not_be_satisfied_by object
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ include ActiveSpec::Specifications
3
+
4
+ context "Not specification" do
5
+ setup do
6
+ @original_spec = PresenceSpecification.new(:foo, :bar)
7
+ @klass = Class.new
8
+ end
9
+
10
+ specify "should not pass if the decorated spec passes" do
11
+ @klass.send(:attr_accessor, :foo)
12
+ @klass.send(:attr_accessor, :bar)
13
+ object = @klass.new
14
+ object.foo = 'make me pass'
15
+ object.bar = 'make me pass'
16
+ @original_spec.should_be_satisfied_by object
17
+ NotSpecification.new(@original_spec).should_not_be_satisfied_by(object)
18
+ end
19
+
20
+ specify "should pass if the decorated spec does not pass" do
21
+ object = @klass.new
22
+ @original_spec.should_not_be_satisfied_by object
23
+ NotSpecification.new(@original_spec).should_be_satisfied_by(object)
24
+ end
25
+ end
@@ -0,0 +1,63 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ include ActiveSpec::Specifications
3
+
4
+ context "Presence specification with one attribute" do
5
+ setup do
6
+ @spec = PresenceSpecification.new(:foo)
7
+ end
8
+
9
+ specify "should pass if the given object has a value for the required attribute" do
10
+ klass = Class.new
11
+ klass.send(:attr_accessor, :foo)
12
+ object = klass.new
13
+ object.foo = 'anything'
14
+ @spec.should_be_satisfied_by object
15
+ end
16
+
17
+ specify "should not pass if the given object doesn't respond to required attribute" do
18
+ klass = Class.new
19
+ @spec.should_not_be_satisfied_by klass.new
20
+ end
21
+
22
+ specify "should not pass if the given object returns nil for required attribute" do
23
+ klass = Class.new
24
+ klass.send(:attr_accessor, :foo)
25
+ object = klass.new
26
+ object.foo = nil
27
+ @spec.should_not_be_satisfied_by object
28
+ end
29
+
30
+ specify "should not pass if the given object returns an empty string for required attribute" do
31
+ klass = Class.new
32
+ klass.send(:attr_accessor, :foo)
33
+ object = klass.new
34
+ object.foo = ''
35
+ @spec.should_not_be_satisfied_by object
36
+ end
37
+ end
38
+
39
+ context "Presence specification with multiple attributes" do
40
+ setup do
41
+ @spec = PresenceSpecification.new(:foo, :bar)
42
+ end
43
+
44
+ specify "should pass if object satisfies spec for all attributes" do
45
+ klass = Class.new
46
+ klass.send(:attr_accessor, :foo)
47
+ klass.send(:attr_accessor, :bar)
48
+ object = klass.new
49
+ object.foo = 'something'
50
+ object.bar = 'anything'
51
+ @spec.should_be_satisfied_by object
52
+ end
53
+
54
+ specify "should not pass if object fails to satisfy spec for at least one attribute" do
55
+ klass = Class.new
56
+ klass.send(:attr_accessor, :foo)
57
+ klass.send(:attr_accessor, :bar)
58
+ object = klass.new
59
+ object.foo = 'something'
60
+ object.bar = ''
61
+ @spec.should_not_be_satisfied_by object
62
+ end
63
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ include ActiveSpec::Specifications
3
+
4
+ context "Proc specification" do
5
+ setup do
6
+ @spec = ProcSpecification.new(Proc.new { |o| o.respond_to?(:foo) })
7
+ end
8
+
9
+ specify "should pass when the proc returns true when called with the given object" do
10
+ klass = Class.new
11
+ klass.send(:attr_reader, :foo)
12
+ @spec.should_be_satisfied_by klass.new
13
+ end
14
+
15
+ specify "should not pass when the proc returns false when called with the given object" do
16
+ @spec.should_not_be_satisfied_by Object.new
17
+ end
18
+ end
19
+
20
+ context "Proc specification with proc that raises" do
21
+ setup do
22
+ @spec = ProcSpecification.new(Proc.new { |o| o.non_existant_method })
23
+ end
24
+
25
+ specify "should never pass" do
26
+ @spec.should_not_be_satisfied_by Object.new
27
+ end
28
+ end
@@ -0,0 +1,74 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ include ActiveSpec::Specifications
3
+
4
+ context "Size specification with a number" do
5
+ setup do
6
+ @spec = SizeSpecification.new(5, :foo)
7
+ end
8
+
9
+ specify "should pass if the given object returns a string of the specified size for the specified attribute" do
10
+ klass = Class.new
11
+ klass.send(:attr_accessor, :foo)
12
+ object = klass.new
13
+ object.foo = 'xxxxx'
14
+ @spec.should_be_satisfied_by object
15
+ end
16
+
17
+ specify "should not pass if the given object does not respond to the specified attribute" do
18
+ @spec.should_not_be_satisfied_by Object.new
19
+ end
20
+
21
+ specify "should not pass if the value of the given object does not respond to size" do
22
+ klass = Class.new
23
+ klass.send(:attr_accessor, :foo)
24
+ object = klass.new
25
+ object.foo = nil
26
+ @spec.should_not_be_satisfied_by object
27
+ end
28
+
29
+ specify "should not pass if the given object does not return a string of the specified size for the specified attribute" do
30
+ klass = Class.new
31
+ klass.send(:attr_accessor, :foo)
32
+ object = klass.new
33
+ object.foo = 'xxxxxxxxx'
34
+ @spec.should_not_be_satisfied_by object
35
+ end
36
+ end
37
+
38
+ context "Size specification with a range" do
39
+ setup do
40
+ @spec = SizeSpecification.new(5..10, :foo)
41
+ end
42
+
43
+ specify "should pass if the given object returns a string of a size within range for the specified attribute" do
44
+ klass = Class.new
45
+ klass.send(:attr_accessor, :foo)
46
+ object = klass.new
47
+ object.foo = 'xxxxx'
48
+ @spec.should_be_satisfied_by object
49
+ object.foo = 'xxxxxxxx'
50
+ @spec.should_be_satisfied_by object
51
+ end
52
+
53
+ specify "should not pass if the given object does not respond to the specified attribute" do
54
+ @spec.should_not_be_satisfied_by Object.new
55
+ end
56
+
57
+ specify "should not pass if the given object does not return a string for the specified attribute" do
58
+ klass = Class.new
59
+ klass.send(:attr_accessor, :foo)
60
+ object = klass.new
61
+ object.foo = DateTime.new
62
+ @spec.should_not_be_satisfied_by object
63
+ end
64
+
65
+ specify "should not pass if the given object does not return a string of a size within range for the specified attribute" do
66
+ klass = Class.new
67
+ klass.send(:attr_accessor, :foo)
68
+ object = klass.new
69
+ object.foo = 'xxxx'
70
+ @spec.should_not_be_satisfied_by object
71
+ object.foo = 'xxxxxxxxxxxxxx'
72
+ @spec.should_not_be_satisfied_by object
73
+ end
74
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require_gem 'rspec'
3
+
4
+ require File.dirname(__FILE__) + '/../lib/active_spec'
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: activespec
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.1"
7
+ date: 2006-09-22 00:00:00 +01:00
8
+ summary: Ruby Specification Library
9
+ require_paths:
10
+ - lib
11
+ email: contact @nospam@ lukeredpath.co.uk
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: rubyslim
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Luke Redpath
31
+ files:
32
+ - lib/active_spec.rb
33
+ - lib/active_spec/base.rb
34
+ - lib/active_spec/context.rb
35
+ - lib/active_spec/satisfies.rb
36
+ - lib/active_spec/specifications.rb
37
+ - lib/active_spec/specifications/attributes_specification.rb
38
+ - lib/active_spec/specifications/collection_specification.rb
39
+ - lib/active_spec/specifications/composite_specification.rb
40
+ - lib/active_spec/specifications/confirmation_specification.rb
41
+ - lib/active_spec/specifications/not_specification.rb
42
+ - lib/active_spec/specifications/presence_specification.rb
43
+ - lib/active_spec/specifications/proc_specification.rb
44
+ - lib/active_spec/specifications/size_specification.rb
45
+ - README
46
+ test_files:
47
+ - spec/collection_specification_spec.rb
48
+ - spec/composite_specification_spec.rb
49
+ - spec/confirmation_specification_spec.rb
50
+ - spec/not_specification_spec.rb
51
+ - spec/presence_specification_spec.rb
52
+ - spec/proc_specification_spec.rb
53
+ - spec/size_specification_spec.rb
54
+ - spec/spec_helper.rb
55
+ rdoc_options: []
56
+
57
+ extra_rdoc_files:
58
+ - README
59
+ executables: []
60
+
61
+ extensions: []
62
+
63
+ requirements: []
64
+
65
+ dependencies: []
66
+