roleplayer 0.1.0
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.
- data/MIT-LICENSE +20 -0
- data/README +13 -0
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/init.rb +2 -0
- data/install.rb +2 -0
- data/lib/generators/roleplayer.rb +27 -0
- data/lib/generators/roleplayer/migration/USAGE +8 -0
- data/lib/generators/roleplayer/migration/migration_generator.rb +14 -0
- data/lib/generators/roleplayer/migration/templates/migration.rb +28 -0
- data/lib/roleplayer.rb +139 -0
- data/roleplayer.gemspec +58 -0
- data/spec/roleplayer/roleplayer_spec.rb +7 -0
- data/spec/spec_helper.rb +48 -0
- data/uninstall.rb +1 -0
- metadata +98 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 András Tarsoly
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rake'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
require 'rspec/core/version'
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'jeweler'
|
11
|
+
Jeweler::Tasks.new do |gem|
|
12
|
+
gem.name = "roleplayer"
|
13
|
+
gem.summary = "Add simple role support to your models."
|
14
|
+
gem.email = "tarsolya@gmail.com"
|
15
|
+
gem.homepage = "http://github.com/tarsolya/roleplayer"
|
16
|
+
gem.authors = ["Tarsoly András"]
|
17
|
+
gem.files = Dir["*", "{lib}/**/*"]
|
18
|
+
gem.add_development_dependency 'rspec-expectations'
|
19
|
+
gem.add_development_dependency 'rspec-mocks'
|
20
|
+
end
|
21
|
+
Jeweler::GemcutterTasks.new
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
24
|
+
end
|
25
|
+
|
26
|
+
Rspec::Core::RakeTask.new(:spec)
|
27
|
+
|
28
|
+
task :spec => :check_dependencies
|
29
|
+
|
30
|
+
task :default => :spec
|
31
|
+
|
32
|
+
require 'rake/rdoctask'
|
33
|
+
desc 'Generate documentation for the has_roles plugin.'
|
34
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
35
|
+
rdoc.rdoc_dir = 'rdoc'
|
36
|
+
rdoc.title = 'Roleplayer'
|
37
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--format=html' << '--template=hanna'
|
38
|
+
rdoc.rdoc_files.include('README')
|
39
|
+
rdoc.rdoc_files.include('lib/*.rb')
|
40
|
+
rdoc.rdoc_files.include('app/**/*.rb')
|
41
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/init.rb
ADDED
data/install.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
module Roleplayer #:nodoc:
|
6
|
+
module Generators #:nodoc:
|
7
|
+
class Base < Rails::Generators::Base #:nodoc:
|
8
|
+
include Rails::Generators::Migration
|
9
|
+
|
10
|
+
def self.source_root
|
11
|
+
@_roleplayer_source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'roleplayer', generator_name, 'templates'))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Required interface for Rails::Generators::Migration
|
15
|
+
def self.next_migration_number(dirname) #:nodoc:
|
16
|
+
if ActiveRecord::Base.timestamped_migrations
|
17
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
18
|
+
else
|
19
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'generators/roleplayer'
|
2
|
+
|
3
|
+
module Roleplayer #:nodoc:#
|
4
|
+
module Generators #:nodoc:#
|
5
|
+
class MigrationGenerator < Base #:nodoc:
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
def create_migration_file
|
9
|
+
migration_template "migration.rb", "db/migrate/roleplayer_migration"
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class RoleplayerMigration < ActiveRecord::Migration #:nodoc:
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
|
5
|
+
# Role table
|
6
|
+
create_table :roles do |t|
|
7
|
+
t.string :name, :null => false
|
8
|
+
end
|
9
|
+
|
10
|
+
# Assignments table
|
11
|
+
create_table :role_assignments do |t|
|
12
|
+
t.references :role, :null => false
|
13
|
+
t.references :assignee, :polymorphic => true, :null => false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Create a unique index for fast lookups and for some ref. integrity
|
17
|
+
add_index :role_assignments,
|
18
|
+
[ :role_id, :assignee_id, :assignee_type ],
|
19
|
+
:unique => true,
|
20
|
+
:name => 'index_role_assigns_by_assignee'
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.down
|
25
|
+
drop_table :roles, :role_assignments
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/roleplayer.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
# Simple role management implementation for ActiveRecord models.
|
5
|
+
module Roleplayer
|
6
|
+
|
7
|
+
#:stopdoc:
|
8
|
+
DEFAULT_OPTIONS = {
|
9
|
+
}.freeze
|
10
|
+
#:startdoc:
|
11
|
+
|
12
|
+
attr_accessor_with_default( :options, {}.merge( Roleplayer::DEFAULT_OPTIONS ) )
|
13
|
+
|
14
|
+
# Defines a model as a roleplayer.
|
15
|
+
#
|
16
|
+
# === Associations
|
17
|
+
#
|
18
|
+
# * +roles+ - Contains the roles assigned to the model instance.
|
19
|
+
# * +role_assignments+ - Polymorphic join model for roles.
|
20
|
+
#
|
21
|
+
# === Scopes
|
22
|
+
#
|
23
|
+
# * <tt>with_any_roles( *args )</tt> - Scope for filtering results based on a list of roles
|
24
|
+
# using the OR operator.
|
25
|
+
#
|
26
|
+
# * <tt>with_all_roles( *args )</tt> - Scope for filtering results based on a list of roles
|
27
|
+
# using the AND operator.
|
28
|
+
#
|
29
|
+
# === Examples
|
30
|
+
#
|
31
|
+
# Foo.find(1).roles #=> [ :editor ]
|
32
|
+
# Foo.find(2).roles #=> [ :admin ]
|
33
|
+
# Foo.find(3).roles #=> [ :editor, :admin ]
|
34
|
+
#
|
35
|
+
# Foo.with_any_roles( :editor, :admin ) #=> [ <Foo:1 ...>, <Foo:2 ...>, <Foo:3 ...> ]
|
36
|
+
# Foo.with_any_roles( :editor ) #=> [ <Foo:1 ...>, <Foo:3 ...> ]
|
37
|
+
# Foo.with_all_roles( :editor, :admin ) #=> [ <Foo:3 ...> ]
|
38
|
+
#
|
39
|
+
# Scopes are chainable:
|
40
|
+
#
|
41
|
+
# Foo.with_any_roles( :editor, :admin ).limit( 1 ) #=> [ <Foo:1 ...> ]
|
42
|
+
#
|
43
|
+
def roleplayer( *args )
|
44
|
+
|
45
|
+
self.options.merge!( args.extract_options! )
|
46
|
+
|
47
|
+
has_many :role_assignments, :class_name => 'RoleAssignment', :as => :assignee, :dependent => :destroy
|
48
|
+
has_many :roles, :through => :role_assignments
|
49
|
+
|
50
|
+
scope :with_any_roles, lambda { |*args|
|
51
|
+
joins( :roles ).
|
52
|
+
where( "roles.name IN (?) ", args.collect { |arg| arg.to_s } )
|
53
|
+
}
|
54
|
+
|
55
|
+
scope :with_all_roles, lambda { |*args|
|
56
|
+
joins(:roles).
|
57
|
+
where( "roles.name IN (?) ", args.collect { |arg| arg.to_s } ).
|
58
|
+
group( "accounts.id" ).
|
59
|
+
having( "count(*) = ?", args.size )
|
60
|
+
}
|
61
|
+
|
62
|
+
include Roleplayer::InstanceMethods
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
module InstanceMethods
|
67
|
+
|
68
|
+
# Compatibility method for +declarative_authorization+, which expect roles
|
69
|
+
# from a model instance as a list of symbols.
|
70
|
+
def role_symbols
|
71
|
+
roles.map { |r| r.name.to_sym }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns true, if the model instance has +role+ assigned to it.
|
75
|
+
#
|
76
|
+
# === Examples
|
77
|
+
#
|
78
|
+
# foo.roles #=> [ :admin, :vip ]
|
79
|
+
#
|
80
|
+
# foo.has_role? ( :admin ) #=> true
|
81
|
+
# foo.has_role? ( "vip" ) #=> true
|
82
|
+
# foo.has_role? ( :invited ) #=> false
|
83
|
+
#
|
84
|
+
def has_role?( role = nil )
|
85
|
+
return false if role.nil?
|
86
|
+
roles.exists?( :name => role.to_s )
|
87
|
+
end
|
88
|
+
|
89
|
+
# Assigns a list of roles to the model instance.
|
90
|
+
# You can use any combination of arguments: they will be flattened
|
91
|
+
# and only previously existing roles will be added.
|
92
|
+
#
|
93
|
+
# === Examples
|
94
|
+
#
|
95
|
+
# foo.add_roles( :admin ) #=> [ :admin ]
|
96
|
+
# foo.add_roles( :admin, :editor ) #=> [ :admin, :editor ]
|
97
|
+
#
|
98
|
+
# When the <tt>:invited</tt> role doesn't exist:
|
99
|
+
#
|
100
|
+
# foo.add_roles( [:admin, :editor], [:invited, :vip] ) #=> [ :admin, :editor, :vip ]
|
101
|
+
#
|
102
|
+
def add_roles( *args )
|
103
|
+
args.flatten!
|
104
|
+
args.each { |arg| roles.push( Role.find_by_name( arg.to_s ) ) if Role.valid?( arg ) }
|
105
|
+
end
|
106
|
+
|
107
|
+
# Delete a list of assigned roles from a model instance.
|
108
|
+
# Same as +add_roles+, arguments will be flattened before performing the delete,
|
109
|
+
# so you can pass multiple arguments at once for convenience.
|
110
|
+
#
|
111
|
+
# === Examples
|
112
|
+
#
|
113
|
+
# foo.roles #=> [ :admin, :editor, :vip ]
|
114
|
+
#
|
115
|
+
# foo.delete_roles( :admin ) #=> [ :editor, :vip ]
|
116
|
+
# foo.delete_roles( [ :editor ], [ :vip, :invited ] ) #=> []
|
117
|
+
#
|
118
|
+
def delete_roles( *args )
|
119
|
+
args.flatten!
|
120
|
+
args.each { |arg| roles.delete( Role.find_by_name( arg.to_s ) ) if has_role?( arg ) }
|
121
|
+
end
|
122
|
+
|
123
|
+
# Removes all roles from the model instance.
|
124
|
+
#
|
125
|
+
# === Examples
|
126
|
+
#
|
127
|
+
# foo.roles #=> [ :admin, :editor, :vip ]
|
128
|
+
#
|
129
|
+
# foo.reset_roles!
|
130
|
+
# foo.roles #=> []
|
131
|
+
#
|
132
|
+
def reset_roles!
|
133
|
+
roles.clear
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
ActiveRecord::Base.class_eval { extend Roleplayer }
|
data/roleplayer.gemspec
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{roleplayer}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Tarsoly András"]
|
12
|
+
s.date = %q{2010-04-22}
|
13
|
+
s.email = %q{tarsolya@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"README"
|
16
|
+
]
|
17
|
+
s.files = [
|
18
|
+
"MIT-LICENSE",
|
19
|
+
"README",
|
20
|
+
"Rakefile",
|
21
|
+
"VERSION",
|
22
|
+
"init.rb",
|
23
|
+
"install.rb",
|
24
|
+
"lib/generators/roleplayer.rb",
|
25
|
+
"lib/generators/roleplayer/migration/USAGE",
|
26
|
+
"lib/generators/roleplayer/migration/migration_generator.rb",
|
27
|
+
"lib/generators/roleplayer/migration/templates/migration.rb",
|
28
|
+
"lib/roleplayer.rb",
|
29
|
+
"roleplayer.gemspec",
|
30
|
+
"uninstall.rb"
|
31
|
+
]
|
32
|
+
s.homepage = %q{http://github.com/tarsolya/roleplayer}
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.6}
|
36
|
+
s.summary = %q{Add simple role support to your models.}
|
37
|
+
s.test_files = [
|
38
|
+
"spec/roleplayer/roleplayer_spec.rb",
|
39
|
+
"spec/spec_helper.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_development_dependency(%q<rspec-expectations>, [">= 0"])
|
48
|
+
s.add_development_dependency(%q<rspec-mocks>, [">= 0"])
|
49
|
+
else
|
50
|
+
s.add_dependency(%q<rspec-expectations>, [">= 0"])
|
51
|
+
s.add_dependency(%q<rspec-mocks>, [">= 0"])
|
52
|
+
end
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<rspec-expectations>, [">= 0"])
|
55
|
+
s.add_dependency(%q<rspec-mocks>, [">= 0"])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
|
2
|
+
require 'rspec/core'
|
3
|
+
|
4
|
+
$LOAD_PATH << File.expand_path('../../../rspec-expectations/lib', __FILE__)
|
5
|
+
$LOAD_PATH << File.expand_path('../../../rspec-mocks/lib', __FILE__)
|
6
|
+
require 'rspec/expectations'
|
7
|
+
require 'rspec/mocks'
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'autotest'
|
11
|
+
rescue LoadError
|
12
|
+
raise "You must install autotest to use it"
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'autotest/rspec2'
|
16
|
+
|
17
|
+
module Rspec
|
18
|
+
module Core
|
19
|
+
module Matchers
|
20
|
+
def fail
|
21
|
+
raise_error(::Rspec::Expectations::ExpectationNotMetError)
|
22
|
+
end
|
23
|
+
|
24
|
+
def fail_with(message)
|
25
|
+
raise_error(::Rspec::Expectations::ExpectationNotMetError, message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def use_formatter(new_formatter)
|
32
|
+
original_formatter = Rspec.configuration.formatter
|
33
|
+
Rspec.configuration.instance_variable_set(:@formatter, new_formatter)
|
34
|
+
yield
|
35
|
+
ensure
|
36
|
+
Rspec.configuration.instance_variable_set(:@formatter, original_formatter)
|
37
|
+
end
|
38
|
+
|
39
|
+
def in_editor?
|
40
|
+
ENV.has_key?('TM_MODE') || ENV.has_key?('EMACS') || ENV.has_key?('VIM')
|
41
|
+
end
|
42
|
+
|
43
|
+
Rspec.configure do |c|
|
44
|
+
c.color_enabled = !in_editor?
|
45
|
+
c.exclusion_filter = { :ruby => lambda {|version|
|
46
|
+
!(RUBY_VERSION.to_s =~ /^#{version.to_s}/)
|
47
|
+
}}
|
48
|
+
end
|
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# No uninstall necessary
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: roleplayer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- "Tarsoly Andr\xC3\xA1s"
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-04-22 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec-expectations
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :development
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rspec-mocks
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 0
|
41
|
+
version: "0"
|
42
|
+
type: :development
|
43
|
+
version_requirements: *id002
|
44
|
+
description:
|
45
|
+
email: tarsolya@gmail.com
|
46
|
+
executables: []
|
47
|
+
|
48
|
+
extensions: []
|
49
|
+
|
50
|
+
extra_rdoc_files:
|
51
|
+
- README
|
52
|
+
files:
|
53
|
+
- MIT-LICENSE
|
54
|
+
- README
|
55
|
+
- Rakefile
|
56
|
+
- VERSION
|
57
|
+
- init.rb
|
58
|
+
- install.rb
|
59
|
+
- lib/generators/roleplayer.rb
|
60
|
+
- lib/generators/roleplayer/migration/USAGE
|
61
|
+
- lib/generators/roleplayer/migration/migration_generator.rb
|
62
|
+
- lib/generators/roleplayer/migration/templates/migration.rb
|
63
|
+
- lib/roleplayer.rb
|
64
|
+
- roleplayer.gemspec
|
65
|
+
- uninstall.rb
|
66
|
+
has_rdoc: true
|
67
|
+
homepage: http://github.com/tarsolya/roleplayer
|
68
|
+
licenses: []
|
69
|
+
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options:
|
72
|
+
- --charset=UTF-8
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.3.6
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Add simple role support to your models.
|
96
|
+
test_files:
|
97
|
+
- spec/roleplayer/roleplayer_spec.rb
|
98
|
+
- spec/spec_helper.rb
|