├── .coveralls.yml
├── lib
├── ya_acl
│ ├── version.rb
│ ├── resource.rb
│ ├── role.rb
│ ├── result.rb
│ ├── assert.rb
│ ├── builder.rb
│ └── acl.rb
└── ya_acl.rb
├── spec
├── spec_helper.rb
└── ya_acl
│ ├── role_spec.rb
│ ├── acl_spec.rb
│ └── builder_spec.rb
├── .gitignore
├── .travis.yml
├── Gemfile
├── Rakefile
├── ya_acl.gemspec
├── LICENSE
└── README.rdoc
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | repo_token: QT2fJfoNCtNjOoT6LDPI9kXaIIOLKtqsZ
2 |
--------------------------------------------------------------------------------
/lib/ya_acl/version.rb:
--------------------------------------------------------------------------------
1 | module YaAcl
2 | VERSION = "0.0.7"
3 | end
4 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'ya_acl'
2 | require 'coveralls'
3 | Coveralls.wear!
4 |
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | pkg/*
2 | *.gem
3 | .bundle
4 | nbproject
5 | .idea
6 | Gemfile.lock
7 | *.swo
8 | *.swp
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 1.8.7
3 | - ree
4 | - 1.9.2
5 | - 1.9.3
6 | - rbx
7 | - jruby
8 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 | gem 'coveralls', :require => false
3 | # Specify your gem's dependencies in ya_acl.gemspec
4 | gemspec
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
3 | require 'rspec/core/rake_task'
4 | RSpec::Core::RakeTask.new(:spec)
5 | task :default => :spec
6 | task :test => :spec
7 |
--------------------------------------------------------------------------------
/lib/ya_acl/resource.rb:
--------------------------------------------------------------------------------
1 | module YaAcl
2 | class Resource
3 | attr_reader :name
4 |
5 | def initialize(name)
6 | @name = name.to_sym
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/ya_acl/role.rb:
--------------------------------------------------------------------------------
1 | module YaAcl
2 | class Role
3 | attr_reader :name, :options
4 | def initialize(name, options = {})
5 | @name = name.to_sym
6 | @options = options
7 | end
8 |
9 | def to_s
10 | self.name
11 | end
12 | end
13 | end
--------------------------------------------------------------------------------
/lib/ya_acl/result.rb:
--------------------------------------------------------------------------------
1 | module YaAcl
2 | class Result
3 | attr_reader :status, :assert, :role
4 |
5 | def initialize(status = true, role = nil, assert = nil)
6 | @status = status
7 | @assert = assert
8 | @role = role
9 | end
10 | end
11 | end
--------------------------------------------------------------------------------
/spec/ya_acl/role_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe YaAcl::Role do
4 | it 'should be instance' do
5 | options = {:name => 'Administrator'}
6 | role = YaAcl::Role.new :admin, options
7 | role.name.should == :admin
8 | role.options.should == options
9 | end
10 | end
--------------------------------------------------------------------------------
/lib/ya_acl.rb:
--------------------------------------------------------------------------------
1 | module YaAcl
2 | autoload :Acl, 'ya_acl/acl'
3 | autoload :Role, 'ya_acl/role'
4 | autoload :Resource, 'ya_acl/resource'
5 | autoload :Assert, 'ya_acl/assert'
6 | autoload :Result, 'ya_acl/result'
7 | autoload :Builder, 'ya_acl/builder'
8 |
9 | class AccessDeniedError < RuntimeError ; end
10 | class AssertAccessDeniedError < AccessDeniedError ; end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/ya_acl/assert.rb:
--------------------------------------------------------------------------------
1 | module YaAcl
2 | class Assert
3 | attr_reader :name
4 |
5 | def initialize(name, param_names, &block)
6 | @name = name.to_sym
7 | @param_names = param_names
8 | @block = block
9 |
10 | @param_names.each do |name|
11 | self.class.send :attr_accessor, name
12 | end
13 | end
14 |
15 | def allow?(params)
16 | if @param_names != (@param_names & params.keys)
17 | raise "Params for assert '#{name}': #{@param_names.inspect}"
18 | end
19 |
20 | @param_names.each do |name|
21 | self.send "#{name}=", params[name]
22 | end
23 |
24 | instance_eval &@block
25 | end
26 | end
27 | end
--------------------------------------------------------------------------------
/ya_acl.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "ya_acl/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "ya_acl"
7 | s.version = YaAcl::VERSION
8 | s.platform = Gem::Platform::RUBY
9 | s.authors = ["Mokevnin Kirill"]
10 | s.email = ["mokevnin@gmail.com"]
11 | s.homepage = "http://github.com/kaize/ya_acl"
12 | s.summary = %q{Yet Another ACL}
13 | s.description = %q{Yet Another ACL}
14 |
15 | # s.rubyforge_project = "ya_acl"
16 |
17 | s.files = `git ls-files`.split("\n")
18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20 | s.require_paths = ["lib"]
21 |
22 | # specify any dependencies here; for example:
23 | s.add_development_dependency "rspec"
24 | s.add_runtime_dependency "rake"
25 | end
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2010 Kirill Mokevnin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/spec/ya_acl/acl_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe YaAcl::Acl do
4 |
5 | before do
6 | @acl = YaAcl::Acl.new
7 | @acl.add_resource(YaAcl::Resource.new(:name))
8 |
9 | @acl.add_role YaAcl::Role.new(:admin)
10 | @acl.add_role YaAcl::Role.new(:moderator)
11 | @acl.add_role YaAcl::Role.new(:editor)
12 | @acl.add_role YaAcl::Role.new(:member)
13 | @acl.add_role YaAcl::Role.new(:guest)
14 |
15 | assert = YaAcl::Assert.new :assert, [:object_user_id, :user_id] do
16 | object_user_id == user_id
17 | end
18 |
19 | @acl.add_assert assert
20 | end
21 |
22 | it 'should be work allow?' do
23 | @acl.allow :name, :index, :admin
24 | @acl.allow :name, :index, :member
25 | @acl.allow :name, :update, :admin
26 |
27 | @acl.allow?(:name, :index, [:admin]).should be_true
28 | @acl.allow?(:name, :index, [:nobody, :admin]).should be_true
29 | @acl.allow?(:name, :index, [:nobody, :another_nobody]).should be_false
30 | @acl.allow?(:name, 'index', ['nobody']).should be_false
31 | @acl.allow?(:name, :index, [:guest]).should be_false
32 | end
33 |
34 | it 'should be work allow? with assert' do
35 | @acl.allow :name, :index, :admin
36 | @acl.allow :name, :index, :guest, :assert
37 | @acl.allow :name, :index, :member, :assert
38 |
39 |
40 | @acl.allow?(:name, :index, [:guest], :object_user_id => 3, :user_id => 4).should be_false
41 | @acl.allow?(:name, :index, [:guest], :object_user_id => 3, :user_id => 3).should be_true
42 | end
43 |
44 | it 'should be work with roles' do
45 | assert = YaAcl::Assert.new :another_assert, [:var] do
46 | var
47 | end
48 | @acl.add_assert assert
49 | @acl.allow :name, :index, :admin
50 | @acl.allow :name, :empty, :admin
51 | @acl.allow :name, :index, :guest, :another_assert
52 |
53 | @acl.allow?(:name, :empty, [:guest, :admin]).should be_true
54 | @acl.allow?(:name, :index, [:guest, :admin], :var => false).should be_true
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/ya_acl/builder.rb:
--------------------------------------------------------------------------------
1 | module YaAcl
2 | class Builder
3 | attr_accessor :acl
4 |
5 | def self.build &block
6 | builder = new block
7 | Acl.instance = builder.acl
8 | end
9 |
10 | def initialize block
11 | self.acl = Acl.new
12 | instance_eval &block
13 | end
14 |
15 | def roles(&block)
16 | instance_eval &block
17 | end
18 |
19 | def asserts(&block)
20 | instance_eval &block
21 | end
22 |
23 | def role(name, options = {})
24 | acl.add_role Role.new(name, options)
25 | end
26 |
27 | def assert(name, param_names, &block)
28 | acl.add_assert Assert.new(name, param_names, &block)
29 | end
30 |
31 | def resources(allow, &block)
32 | @global_allow_role = allow
33 | instance_eval &block
34 | end
35 |
36 | def resource(name, allow_roles = [], &block)
37 | raise ArgumentError, 'Options "allow_roles" must be Array' unless allow_roles.is_a? Array
38 | resource_allow_roles = allow_roles << @global_allow_role
39 | resource = Resource.new(name)
40 | acl.add_resource resource
41 | PrivilegeProxy.new(resource.name, resource_allow_roles, acl, block)
42 | end
43 |
44 | class PrivilegeProxy
45 | def initialize(name, allow_roles, acl, block)
46 | @resource_name = name
47 | @allow_roles = allow_roles
48 | @acl = acl
49 | instance_eval &block
50 | end
51 |
52 | def privilege(privilege_name, roles = [], &asserts_block)
53 | all_allow_roles = roles | @allow_roles
54 |
55 | asserts = {}
56 | if block_given?
57 | asserts = PrivilegeAssertProxy.build asserts_block, all_allow_roles
58 | end
59 |
60 | all_allow_roles.each do |role|
61 | if asserts[role]
62 | asserts[role].each do |assert|
63 | @acl.allow(@resource_name, privilege_name, role, assert)
64 | end
65 | else
66 | @acl.allow(@resource_name, privilege_name, role, nil)
67 | end
68 | end
69 | end
70 | end
71 |
72 | class PrivilegeAssertProxy
73 | attr_reader :asserts
74 |
75 | def self.build(block, all_allow_roles)
76 | builder = new block, all_allow_roles
77 | builder.asserts
78 | end
79 |
80 | def initialize(block, all_allow_roles)
81 | @all_allow_roles = all_allow_roles
82 | @asserts = {}
83 | instance_eval &block
84 | end
85 |
86 | def assert(name, roles = [])
87 | roles = @all_allow_roles unless roles.any?
88 | roles.each do |role|
89 | @asserts[role] ||= []
90 | @asserts[role] << name
91 | end
92 | end
93 | end
94 | end
95 | end
96 |
--------------------------------------------------------------------------------
/lib/ya_acl/acl.rb:
--------------------------------------------------------------------------------
1 | module YaAcl
2 | class Acl
3 |
4 | attr_reader :roles, :resources, :asserts
5 |
6 | class << self
7 | def instance
8 | @@acl
9 | end
10 |
11 | def instance=(v)
12 | @@acl = v
13 | end
14 | end
15 |
16 | def initialize()
17 | @acl = {}
18 | end
19 |
20 | def add_role(role)
21 | @roles ||= {}
22 | @roles[role.name] = role
23 | end
24 |
25 | def role(role_name)
26 | if !defined?(@roles) || !@roles[role_name.to_sym]
27 | raise ArgumentError, "#Role '#{role_name}' doesn't exists"
28 | end
29 | @roles[role_name.to_sym]
30 | end
31 |
32 | def add_resource(resource)
33 | @resources ||= {}
34 | @resources[resource.name] = resource
35 | end
36 |
37 | def resource(resource_name)
38 | if !defined?(@resources) || !@resources[resource_name.to_sym]
39 | raise ArgumentError, "#Resource '#{resource_name}' doesn't exists"
40 | end
41 | @resources[resource_name.to_sym]
42 | end
43 |
44 | def privilege(resource_name, privilege_name)
45 | r = resource(resource_name)
46 | p = privilege_name.to_sym
47 | unless @acl[r.name][p]
48 | raise ArgumentError, "Undefine privilege '#{privilege_name}' for resource '#{resource_name}'"
49 | end
50 |
51 | @acl[r.name][p]
52 | end
53 |
54 | def add_assert(assert)
55 | @asserts ||= {}
56 | @asserts[assert.name] = assert
57 | end
58 |
59 | def assert(assert_name)
60 | if !defined?(@asserts) || !@asserts[assert_name.to_sym]
61 | raise ArgumentError, "#Assert '#{assert_name}' doesn't exists"
62 | end
63 | @asserts[assert_name.to_sym]
64 | end
65 |
66 | def allow(resource_name, privilege_name, role_name, assert_name = nil)
67 | resource = resource(resource_name).name
68 | privilege = privilege_name.to_sym
69 | role = role(role_name).name
70 |
71 | @acl[resource] ||= {}
72 | @acl[resource][privilege] ||= {}
73 | @acl[resource][privilege][role] ||= {}
74 | if assert_name
75 | assert = assert(assert_name)
76 | @acl[resource][privilege][role][assert.name] = assert
77 | end
78 | end
79 |
80 | def check(resource_name, privilege_name, roles = [], params = {})
81 | a_l = privilege(resource_name, privilege_name)
82 | roles_for_check = a_l.keys & roles.map(&:to_sym)
83 | return Result.new(false) if roles_for_check.empty? # return
84 |
85 | role_for_result = nil
86 | assert_for_result = nil
87 | roles_for_check.each do |role|
88 | role_for_result = role
89 | asserts = a_l[role]
90 | return Result.new if asserts.empty? #return
91 | result = true
92 | asserts.values.each do |assert|
93 | assert_for_result = assert
94 | result = assert.allow?(params)
95 | break unless result
96 | end
97 | if result
98 | return Result.new # return
99 | end
100 | end
101 |
102 | Result.new(false, role_for_result, assert_for_result) # return
103 | end
104 |
105 | def allow?(resource_name, privilege_name, roles = [], params = {})
106 | check(resource_name, privilege_name, roles, params).status
107 | end
108 |
109 | def check!(resource_name, privilege_name, roles = [], params = {})
110 | result = check(resource_name, privilege_name, roles, params)
111 | return true if result.status
112 |
113 | message = "Access denied for '#{resource_name}', privilege '#{privilege_name}'"
114 | if result.assert
115 | raise AssertAccessDeniedError, message + ", role '#{result.role}' and assert '#{result.assert.name}'"
116 | else
117 | raise AccessDeniedError, message + " and roles '#{roles.inspect}'"
118 | end
119 | end
120 | end
121 | end
122 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | == ya_acl
2 |
3 | {
}[http://travis-ci.org/kaize/ya_acl]
4 | {
}[https://coveralls.io/r/kaize/ya_acl]
5 |
6 | Ya_Acl - access control list (ACL) implementation for your Ruby application.
7 |
8 | Ya_Acl provides a standalone object through which all checks are made.
9 | This means it is not tied to any framework. Note that this guide will show you only one possible way to use this component.
10 |
11 | === Installation
12 |
13 | gem install ya_acl
14 |
15 | === Keywords
16 |
17 | Resource - object to restrict access to.
18 | Privilege - action on the resource.
19 | Role - object, which can request for an access to a resource.
20 |
21 | Role(s) request for an access to the resource privileges.
22 | For example, resource "user" can have a privilege "create".
23 |
24 | === Initial conditions
25 |
26 | - By default, everything is forbidden. Further you will only be able to grant access to a particular resource, not restrict it.
27 | - All resources must be added to the acl (otherwise you will get an exception).
28 |
29 | === Key features
30 |
31 | Asserts - runtime checks, e.g. "whether logged in user is the owner of this object".
32 | Checks can be assigned to specific roles of the current privilege, not just "on the privilege".
33 | Owning multiple roles. If at least one of the user roles has access to the resource privilege,
34 | access granted. Role with global access to all resources. Passed as an argument to the `Builder::resources`
35 | method. Roles inheritance. That is, we could define a role that will automatically get all resource privileges.
36 |
37 | === Access check algorithm
38 |
39 | 1. If none of the passed roles have access to resource privilege - access denied.
40 | 2. If any, for each role we run asserts. If at least one role passed these checks - access granted.
41 |
42 | === Workflow
43 |
44 | First, initialize acl object by creating the config file
45 | (you could use the structure sample below). It should be loaded while your application starts.
46 | Although, in development environment, you may want it to be loaded before each request.
47 |
48 | YaAcl::Builder.build do
49 | roles do # Roles
50 | role :admin
51 | role :editor
52 | role :operator
53 | end
54 |
55 | asserts do # Checks
56 | assert :assert_name, [:current_user_id, :another_user_id] do
57 | current_user_id == another_user_id
58 | end
59 |
60 | assert :another_assert_name, [:current_user_id, :another_user_id] do
61 | current_user_id != another_user_id
62 | end
63 | end
64 |
65 | resources :admin do # Resources and role with admin privileges
66 | resource 'UserController', [:editor] do # Resource and roles, which have access to the all privileges of a given resource
67 | privilege :index, [:operator] # allowed for :admin, :editor, :operator
68 | privilege :edit # allowed for :admin, :editor
69 | privilege :new do
70 | assert :assert_name, [:editor] # This check will be called for role :editor
71 | assert :another_assert_name # This check will be called for :admin and :editor roles
72 | end
73 | end
74 | end
75 | end
76 |
77 | After that, acl object becomes accessible via YaAcl::Acl.instance.
78 |
79 | acl = YaAcl::Acl.instance
80 |
81 | acl.allow?('UserController', :index, [:editor, :opeartor]) # true
82 | acl.allow?('UserController', :edit, [:editor, :opeartor]) # true
83 | acl.allow?('UserController', :edit, [:opeartor]) # false
84 | acl.allow?('UserController', :new, [:admin], :current_user_id => 1, :another_user_id => 1) # true
85 | acl.allow?('UserController', :new, [:editor], :current_user_id => 1, :another_user_id => 2) # false
86 |
87 | acl#check - returns YaAcl::Result object
88 | acl#check! - returns true or throws an exception
89 |
--------------------------------------------------------------------------------
/spec/ya_acl/builder_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe YaAcl::Builder do
4 | it 'should be add role' do
5 | acl = YaAcl::Builder.build do
6 | roles do
7 | role :admin, :name => 'Administrator'
8 | end
9 | end
10 |
11 | acl.role(:admin).should_not be_nil
12 | end
13 |
14 | it 'should be add resource' do
15 | acl = YaAcl::Builder.build do
16 | roles do
17 | role :admin
18 | role :another_admin
19 | role :user
20 | role :operator
21 | end
22 |
23 | asserts do
24 | assert :first_assert, [:param, :param2] do
25 | param == param2
26 | end
27 |
28 | assert :another_assert, [:param, :param2] do
29 | param != param2
30 | end
31 | end
32 |
33 | resources :admin do
34 | resource :name, [:another_admin] do
35 | privilege :index, [:operator]
36 | privilege :show, [:operator]
37 | privilege :edit
38 | privilege :with_assert, [:operator] do
39 | assert :first_assert
40 | assert :another_assert, [:admin]
41 | end
42 | end
43 | end
44 | end
45 |
46 | acl.check!(:name, :index, [:admin]).should be_true
47 | acl.check!(:name, :index, [:another_admin]).should be_true
48 | acl.check!(:name, :index, [:operator]).should be_true
49 |
50 | acl.allow?(:name, :show, [:admin]).should be_true
51 | acl.allow?(:name, :show, [:user]).should be_false
52 | acl.allow?(:name, :show, [:operator]).should be_true
53 | acl.allow?(:name, :edit, [:operator]).should be_false
54 | end
55 |
56 | it 'should be raise exception for unknown role in privilegy' do
57 | lambda {
58 | YaAcl::Builder.build do
59 | roles do
60 | role :admin, :name => 'Administrator'
61 | end
62 | resources :admin do
63 | resource 'resource' do
64 | privilege :index, [:operator]
65 | end
66 | end
67 | end
68 | }.should raise_exception(ArgumentError)
69 | end
70 |
71 | it 'should be raise exception for unknown role in resource' do
72 | lambda {
73 | YaAcl::Builder.build do
74 | roles do
75 | role :admin, :name => 'Administrator'
76 | role :operator
77 | end
78 |
79 | resources :admin do
80 | resource 'resource', [:another_admin] do
81 | privilege :index, [:opeartor]
82 | end
83 | end
84 | end
85 | }.should raise_exception(ArgumentError)
86 | end
87 |
88 | it 'should be work with asserts' do
89 | acl = YaAcl::Builder.build do
90 | roles do
91 | role :admin
92 | role :another_user
93 | role :editor
94 | role :operator
95 | end
96 |
97 | asserts do
98 | assert :first, [:var] do
99 | var
100 | end
101 |
102 | assert :another, [:first] do
103 | statuses = [1, 2]
104 | statuses.include? first
105 | end
106 |
107 | assert :another2, [:first] do
108 | !!first
109 | end
110 |
111 | assert :another3, [:first] do
112 | statuses = [1, 2]
113 | statuses.include? first
114 | end
115 |
116 | assert :another4, [:first, :second] do
117 | first == second
118 | end
119 | end
120 |
121 | resources :admin do
122 | resource :name, [:editor, :operator] do
123 | privilege :create do
124 | assert :first, [:admin, :another_user]
125 | end
126 | privilege :update do
127 | assert :another, [:editor]
128 | assert :another2, [:editor, :operator]
129 | assert :another3, [:operator]
130 | assert :another4, [:operator]
131 | end
132 | end
133 | end
134 | end
135 |
136 | acl.allow?(:name, :update, [:another_user]).should be_false
137 | acl.allow?(:name, :update, [:editor], :first => true, :second => false).should be_false
138 | acl.allow?(:name, :update, [:editor], :first => false, :second => true).should be_false
139 | acl.allow?(:name, :update, [:editor], :first => 1, :second => true).should be_true
140 | acl.check!(:name, :create, [:admin], :var => 2).should be_true
141 | acl.allow?(:name, :update, [:editor], :first => 3, :second => false).should be_false
142 | acl.allow?(:name, :update, [:operator], :first => true, :second => true).should be_false
143 | acl.allow?(:name, :update, [:operator], :first => 1, :second => 1).should be_true
144 | acl.allow?(:name, :update, [:operator], :first => 3, :second => 3).should be_false
145 | end
146 | end
147 |
--------------------------------------------------------------------------------