confluencer 0.2.7 → 0.2.8
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/VERSION +1 -1
- data/confluencer.gemspec +9 -3
- data/lib/confluence/client.rb +95 -36
- data/lib/confluence/record.rb +20 -17
- data/lib/confluence/session.rb +28 -14
- data/lib/confluencer.rb +7 -0
- data/spec/confluence.yaml.example +27 -1
- data/spec/confluence/bookmark_spec.rb +4 -0
- data/spec/confluence/client_spec.rb +74 -0
- data/spec/confluence/page_spec.rb +3 -3
- data/spec/confluence/record_spec.rb +9 -0
- data/spec/spec_helper.rb +6 -1
- metadata +9 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.8
|
data/confluencer.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{confluencer}
|
8
|
-
s.version = "0.2.
|
8
|
+
s.version = "0.2.8"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Gabor Ratky"]
|
12
|
-
s.date = %q{2010-05-
|
12
|
+
s.date = %q{2010-05-11}
|
13
13
|
s.description = %q{ActiveRecord-like classes to access Confluence through XMLRPC.}
|
14
14
|
s.email = %q{rgabo@rgabostyle.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -37,7 +37,10 @@ Gem::Specification.new do |s|
|
|
37
37
|
"script/console",
|
38
38
|
"script/console_init.rb",
|
39
39
|
"spec/confluence.yaml.example",
|
40
|
+
"spec/confluence/bookmark_spec.rb",
|
41
|
+
"spec/confluence/client_spec.rb",
|
40
42
|
"spec/confluence/page_spec.rb",
|
43
|
+
"spec/confluence/record_spec.rb",
|
41
44
|
"spec/confluence/session_spec.rb",
|
42
45
|
"spec/confluencer_spec.rb",
|
43
46
|
"spec/spec.opts",
|
@@ -49,7 +52,10 @@ Gem::Specification.new do |s|
|
|
49
52
|
s.rubygems_version = %q{1.3.6}
|
50
53
|
s.summary = %q{Useful classes to manage Confluence.}
|
51
54
|
s.test_files = [
|
52
|
-
"spec/confluence/
|
55
|
+
"spec/confluence/bookmark_spec.rb",
|
56
|
+
"spec/confluence/client_spec.rb",
|
57
|
+
"spec/confluence/page_spec.rb",
|
58
|
+
"spec/confluence/record_spec.rb",
|
53
59
|
"spec/confluence/session_spec.rb",
|
54
60
|
"spec/confluencer_spec.rb",
|
55
61
|
"spec/spec_helper.rb"
|
data/lib/confluence/client.rb
CHANGED
@@ -1,67 +1,126 @@
|
|
1
1
|
require 'xmlrpc/client'
|
2
2
|
|
3
|
-
#
|
4
|
-
|
5
|
-
# A useful helper for running Confluence XML-RPC from Ruby. Takes care of
|
6
|
-
# adding the token to each method call (so you can call server.getSpaces()
|
7
|
-
# instead of server.getSpaces(token)). Also takes care of re-logging in
|
8
|
-
# if your login times out.
|
9
|
-
#
|
10
|
-
# Usage:
|
11
|
-
#
|
12
|
-
# client = Confluence::Client.new(:url => "http://confluence.atlassian.com")
|
13
|
-
# client.login("user", "password")
|
14
|
-
# puts client.getSpaces()
|
15
|
-
#
|
3
|
+
# Module containing Confluence-related classes.
|
16
4
|
module Confluence
|
5
|
+
# Originally confluence4r, available at: http://confluence.atlassian.com/display/DISC/Confluence4r
|
6
|
+
|
7
|
+
# A useful helper for running Confluence XML-RPC from Ruby. Takes care of
|
8
|
+
# adding the token to each method call (so you can call server.getSpaces()
|
9
|
+
# instead of server.getSpaces(token)).
|
10
|
+
#
|
11
|
+
# Usage:
|
12
|
+
#
|
13
|
+
# client = Confluence::Client.new(:url => "http://confluence.atlassian.com")
|
14
|
+
# client.login("user", "password")
|
15
|
+
# p client.getSpaces
|
16
|
+
#
|
17
17
|
class Client
|
18
18
|
PREFIX = "confluence1"
|
19
|
+
XMLRPC_SUFFIX = "/rpc/xmlrpc"
|
19
20
|
|
20
|
-
attr_reader :username, :token
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
attr_reader :url, :username, :token
|
22
|
+
|
23
|
+
# Initializes a new client with the given arguments.
|
24
|
+
#
|
25
|
+
# ==== Parameters
|
26
|
+
# arguments<Hash>:: Described below.
|
27
|
+
#
|
28
|
+
# ==== Arguments
|
29
|
+
# :url - The url of the Confluence instance. The trailing '/rpc/xmlrpc' path is optional.
|
30
|
+
# :token - An existing session token to reuse.
|
31
|
+
#
|
32
|
+
def initialize(arguments = {})
|
33
|
+
@url = arguments[:url]
|
34
|
+
@token = arguments[:token]
|
28
35
|
|
29
|
-
|
30
|
-
@token
|
36
|
+
Log4r::MDC.put('token', @token || 'nil')
|
37
|
+
log.info "initialized client (:url => #{@url}, :token => #{@token || 'nil'})"
|
31
38
|
end
|
32
|
-
|
39
|
+
|
40
|
+
# Returns true, if the client has a session token.
|
41
|
+
#
|
33
42
|
def has_token?
|
34
43
|
!@token.nil?
|
35
44
|
end
|
36
45
|
|
46
|
+
# Logs in and returns the newly acquired session token.
|
47
|
+
#
|
48
|
+
# ==== Parameters
|
49
|
+
# username<String>:: The username.
|
50
|
+
# password<String>:: The password.
|
51
|
+
#
|
37
52
|
def login(username, password)
|
38
|
-
|
39
|
-
|
53
|
+
handle_fault do
|
54
|
+
if @token = proxy.login(username, password)
|
55
|
+
Log4r::MDC.put('token', @token)
|
56
|
+
log.info "logged in as '#{username}' and acquired token."
|
40
57
|
|
41
|
-
|
42
|
-
|
58
|
+
@username = username
|
59
|
+
@password = password
|
60
|
+
end
|
43
61
|
end
|
62
|
+
|
63
|
+
@token
|
44
64
|
end
|
45
65
|
|
66
|
+
# Logs out and invalidates the session token.
|
67
|
+
#
|
46
68
|
def logout
|
47
|
-
|
48
|
-
|
69
|
+
handle_fault do
|
70
|
+
@token = nil if @token and result = proxy.logout(@token)
|
71
|
+
log.info "logged out"
|
72
|
+
Log4r::MDC.put('token', 'nil')
|
73
|
+
result
|
49
74
|
end
|
50
75
|
end
|
51
76
|
|
77
|
+
# Translates every call into XMLRPC calls.
|
78
|
+
#
|
52
79
|
def method_missing(method_name, *args)
|
53
|
-
|
54
|
-
|
80
|
+
handle_fault do
|
81
|
+
if args.empty?
|
82
|
+
log.debug "#{method_name}"
|
83
|
+
else
|
84
|
+
log.debug "#{method_name}(#{args.join(', ')})"
|
85
|
+
end
|
86
|
+
result = proxy.send(method_name, *([@token] + args))
|
87
|
+
log.debug(result.inspect)
|
88
|
+
result
|
55
89
|
end
|
56
90
|
end
|
57
91
|
|
58
92
|
private
|
59
|
-
|
60
|
-
|
93
|
+
|
94
|
+
# Returns the Confluence::Client logger.
|
95
|
+
def log
|
96
|
+
Log4r::Logger[Confluence::Client.to_s] || Log4r::Logger.root
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the Confluence XMLRPC endpoint url.
|
100
|
+
#
|
101
|
+
def xmlrpc_url
|
102
|
+
unless @url[-11..-1] == XMLRPC_SUFFIX
|
103
|
+
@url + XMLRPC_SUFFIX
|
104
|
+
else
|
105
|
+
@url
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the XMLRPC client proxy for the Confluence API v1.
|
110
|
+
#
|
111
|
+
def proxy
|
112
|
+
@proxy ||= XMLRPC::Client.new_from_uri(xmlrpc_url).proxy(PREFIX)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Yields and translates any XMLRPC::FaultExceptions raised by Confluence to Confluence::Errors.
|
116
|
+
#
|
117
|
+
def handle_fault(&block)
|
61
118
|
begin
|
62
|
-
block.call
|
119
|
+
block.call
|
63
120
|
rescue XMLRPC::FaultException => e
|
64
|
-
|
121
|
+
log.warn message = e.faultString.rpartition(':').last
|
122
|
+
|
123
|
+
case message
|
65
124
|
when /Transaction rolled back/
|
66
125
|
raise Confluence::Error, "Could not save or update record."
|
67
126
|
else
|
data/lib/confluence/record.rb
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
module Confluence
|
2
2
|
class Record
|
3
3
|
class << self
|
4
|
+
def client
|
5
|
+
raise "Confluence client is unavailable. Did you forget to use Confluence::Session.new?" unless @@client
|
6
|
+
@@client
|
7
|
+
end
|
8
|
+
|
9
|
+
def client=(value)
|
10
|
+
@@client = value
|
11
|
+
end
|
12
|
+
|
4
13
|
def record_attr_accessor(*args)
|
5
|
-
|
6
14
|
attributes = {}
|
7
15
|
|
8
16
|
# iterate through each argument
|
@@ -31,7 +39,12 @@ module Confluence
|
|
31
39
|
attr_accessor :attributes
|
32
40
|
|
33
41
|
def initialize(hash)
|
34
|
-
@attributes =
|
42
|
+
@attributes = {}
|
43
|
+
|
44
|
+
# iterate through each key/value pair and set attribute keyed by symbol
|
45
|
+
hash.each_pair do |key, value|
|
46
|
+
self[key.to_sym] = value
|
47
|
+
end
|
35
48
|
end
|
36
49
|
|
37
50
|
def [](attr)
|
@@ -47,27 +60,17 @@ module Confluence
|
|
47
60
|
end
|
48
61
|
|
49
62
|
def labels
|
50
|
-
client.getLabelsById(record_id).collect {|label| label["name"]}
|
63
|
+
@labels ||= client.getLabelsById(record_id).collect {|label| label["name"]}
|
51
64
|
end
|
52
65
|
|
53
66
|
def labels=(value)
|
54
|
-
|
55
|
-
|
56
|
-
added_labels = value - existing_labels
|
67
|
+
removed_labels = labels - value
|
68
|
+
added_labels = value - labels
|
57
69
|
|
58
70
|
client.removeLabelByName(removed_labels.join(" "), record_id) unless removed_labels.empty?
|
59
71
|
client.addLabelByName(added_labels.join(" "), record_id) unless added_labels.empty?
|
60
|
-
|
61
|
-
|
62
|
-
@@client = nil
|
63
|
-
|
64
|
-
def self.client
|
65
|
-
raise "Confluence client is unavailable. Did you forget to use Confluence::Session.new?" unless @@client
|
66
|
-
@@client
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.client=(client)
|
70
|
-
@@client = client
|
72
|
+
|
73
|
+
@labels = value
|
71
74
|
end
|
72
75
|
|
73
76
|
def client
|
data/lib/confluence/session.rb
CHANGED
@@ -1,11 +1,22 @@
|
|
1
1
|
module Confluence
|
2
|
+
# Wraps a Confluence::Client and manages the lifetime of a session.
|
3
|
+
#
|
2
4
|
class Session
|
3
5
|
attr_reader :client
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
# Initializes a new session with the given arguments and sets it for other classes like Confluence::Page.
|
8
|
+
#
|
9
|
+
# If a block is given to initialize, initialize yields with the Confluence::Client and automatically logs out of the session afterwards.
|
10
|
+
# Otherwise Session#destroy should be called after finished.
|
11
|
+
#
|
12
|
+
# ==== Parameters
|
13
|
+
# arguments<Hash>:: Described below.
|
14
|
+
#
|
15
|
+
# ==== Arguments
|
16
|
+
# :url - The url of the Confluence instance.
|
17
|
+
# :username - The username.
|
18
|
+
# :password - The password.
|
19
|
+
#
|
9
20
|
def initialize(arguments = {})
|
10
21
|
raise ArgumentError, "Required argument 'url' is missing." unless arguments.key? :url
|
11
22
|
|
@@ -19,30 +30,33 @@ module Confluence
|
|
19
30
|
end
|
20
31
|
|
21
32
|
# set client for records
|
22
|
-
Confluence::Record
|
33
|
+
Confluence::Record.client = @client
|
23
34
|
|
35
|
+
# yield if block was given and destroy afterwards
|
24
36
|
if block_given?
|
25
|
-
|
26
|
-
yield @client
|
27
|
-
rescue RuntimeError => e
|
28
|
-
# strip non-message part of java exception message
|
29
|
-
raise e.message.split(":").last.strip
|
30
|
-
end
|
37
|
+
yield @client
|
31
38
|
|
32
39
|
self.destroy
|
33
40
|
end
|
34
41
|
end
|
35
42
|
|
43
|
+
# Returns the current session token.
|
44
|
+
#
|
45
|
+
def token
|
46
|
+
client.token if client
|
47
|
+
end
|
48
|
+
|
49
|
+
# Destroys the session by logging out and resets other classes like Confluence::Page.
|
50
|
+
#
|
36
51
|
def destroy
|
37
52
|
# invalidate the token
|
38
53
|
client.logout
|
39
54
|
|
40
|
-
# client
|
55
|
+
# client is not valid anymore
|
41
56
|
@client = nil
|
42
|
-
@token = nil
|
43
57
|
|
44
58
|
# reset client for records
|
45
|
-
Confluence::Record
|
59
|
+
Confluence::Record.client = nil
|
46
60
|
end
|
47
61
|
end
|
48
62
|
end
|
data/lib/confluencer.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# ensure that lib is in the load path
|
2
2
|
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
3
|
|
4
|
+
require 'rubygems'
|
5
|
+
require 'log4r'
|
6
|
+
|
4
7
|
require 'confluence/error'
|
5
8
|
|
6
9
|
require 'confluence/client'
|
@@ -10,3 +13,7 @@ require 'confluence/record'
|
|
10
13
|
require 'confluence/page'
|
11
14
|
require 'confluence/bookmark'
|
12
15
|
require 'confluence/blog_entry'
|
16
|
+
|
17
|
+
module Confluencer
|
18
|
+
VERSION = "0.2.7"
|
19
|
+
end
|
@@ -4,4 +4,30 @@
|
|
4
4
|
:username: roger
|
5
5
|
:password: jessica
|
6
6
|
:space: confluencer
|
7
|
-
:page_title: Confluencer RSpec Test Page
|
7
|
+
:page_title: Confluencer RSpec Test Page
|
8
|
+
|
9
|
+
# Log4r configuration
|
10
|
+
log4r_config:
|
11
|
+
pre_config:
|
12
|
+
global:
|
13
|
+
level: DEBUG
|
14
|
+
root:
|
15
|
+
level: DEBUG
|
16
|
+
|
17
|
+
# loggers
|
18
|
+
loggers:
|
19
|
+
- name : Confluence::Client
|
20
|
+
level : DEBUG
|
21
|
+
additive : 'false'
|
22
|
+
trace : 'false'
|
23
|
+
outputters:
|
24
|
+
- stdout
|
25
|
+
|
26
|
+
# outputters
|
27
|
+
outputters:
|
28
|
+
- type : StdoutOutputter
|
29
|
+
name : stdout
|
30
|
+
level : DEBUG
|
31
|
+
formatter:
|
32
|
+
type : PatternFormatter
|
33
|
+
pattern : '%d %C (%X{token}) [%l]: %m'
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe Confluence::Client do
|
4
|
+
include ConfigurationHelperMethods
|
5
|
+
|
6
|
+
def new_client_from_config
|
7
|
+
Confluence::Client.new(config)
|
8
|
+
end
|
9
|
+
|
10
|
+
def logged_in_client
|
11
|
+
client = new_client_from_config
|
12
|
+
client.login(config[:username], config[:password])
|
13
|
+
client.has_token?.should be_true
|
14
|
+
client
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can initialize a client with a url" do
|
18
|
+
client = Confluence::Client.new :url => "http://confluence.atlassian.com"
|
19
|
+
|
20
|
+
client.should_not be_nil
|
21
|
+
client.url.should_not be_nil
|
22
|
+
client.url.should == "http://confluence.atlassian.com"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "can initialize a client with an existing token" do
|
26
|
+
client = Confluence::Client.new :url => "http://confluence.atlassian.com", :token => "abcdef"
|
27
|
+
client.token.should_not be_nil
|
28
|
+
client.token.should == "abcdef"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "can return whether a token is already available" do
|
32
|
+
client = Confluence::Client.new :url => "http://confluence.atlassian.com"
|
33
|
+
client.has_token?.should be_false
|
34
|
+
|
35
|
+
client = Confluence::Client.new :url => "http://confluence.atlassian.com", :token => "abcdef"
|
36
|
+
client.has_token?.should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
it "can log in and acquire a session token" do
|
40
|
+
client = new_client_from_config
|
41
|
+
client.has_token?.should be_false
|
42
|
+
|
43
|
+
token = client.login(config[:username], config[:password])
|
44
|
+
token.should_not be_nil
|
45
|
+
|
46
|
+
client.has_token?.should be_true
|
47
|
+
client.token.should == token
|
48
|
+
end
|
49
|
+
|
50
|
+
it "raises an error if it cannot login" do
|
51
|
+
client = new_client_from_config
|
52
|
+
|
53
|
+
lambda { client.login(config[:username], "bogus") }.should raise_exception(Confluence::Error, /incorrect password/)
|
54
|
+
client.has_token?.should be_false
|
55
|
+
|
56
|
+
lambda { client.login("bogus", "bogus") }.should raise_exception(Confluence::Error, /no user could be found/)
|
57
|
+
client.has_token?.should be_false
|
58
|
+
end
|
59
|
+
|
60
|
+
it "can logout and invalidate a session token" do
|
61
|
+
client = logged_in_client
|
62
|
+
|
63
|
+
client.logout.should be_true
|
64
|
+
client.has_token?.should be_false
|
65
|
+
end
|
66
|
+
|
67
|
+
it "can make XMLRPC calls" do
|
68
|
+
client = logged_in_client
|
69
|
+
|
70
|
+
server_info = client.getServerInfo
|
71
|
+
server_info.should_not be_nil
|
72
|
+
server_info["baseUrl"].should == client.url
|
73
|
+
end
|
74
|
+
end
|
@@ -21,9 +21,9 @@ describe Confluence::Page do
|
|
21
21
|
new_session do
|
22
22
|
begin
|
23
23
|
# check whether we need to remove the test page
|
24
|
-
|
24
|
+
test_page = Confluence::Page.find_by_title config[:space], config[:page_title]
|
25
25
|
test_page.remove if test_page
|
26
|
-
rescue
|
26
|
+
rescue Confluence::Error
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -31,7 +31,7 @@ describe Confluence::Page do
|
|
31
31
|
it "should add a new page in Confluence" do
|
32
32
|
page = nil
|
33
33
|
|
34
|
-
new_session do
|
34
|
+
new_session do |client|
|
35
35
|
# initialize test page
|
36
36
|
page = create_test_page
|
37
37
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe Confluence::Record do
|
4
|
+
it "should initialize new record from Confluence hash" do
|
5
|
+
record = Confluence::Record.new :foo => 'bar'
|
6
|
+
|
7
|
+
record[:foo].should == 'bar'
|
8
|
+
end
|
9
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -6,7 +6,12 @@ require 'spec'
|
|
6
6
|
require 'spec/autorun'
|
7
7
|
require 'yaml'
|
8
8
|
|
9
|
+
require 'log4r/yamlconfigurator'
|
10
|
+
|
9
11
|
Spec::Runner.configure do |config|
|
12
|
+
config.before :suite do
|
13
|
+
Log4r::YamlConfigurator.load_yaml_file(File.join(File.dirname(__FILE__), 'confluence.yaml'))
|
14
|
+
end
|
10
15
|
end
|
11
16
|
|
12
17
|
module ConfigurationHelperMethods
|
@@ -22,7 +27,7 @@ module SessionHelperMethods
|
|
22
27
|
def new_session
|
23
28
|
if block_given?
|
24
29
|
# initialize session and yield
|
25
|
-
Confluence::Session.new
|
30
|
+
Confluence::Session.new(config) {|client| yield client }
|
26
31
|
else
|
27
32
|
# initialize session and return
|
28
33
|
Confluence::Session.new config
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 8
|
9
|
+
version: 0.2.8
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Gabor Ratky
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-05-
|
17
|
+
date: 2010-05-11 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -61,7 +61,10 @@ files:
|
|
61
61
|
- script/console
|
62
62
|
- script/console_init.rb
|
63
63
|
- spec/confluence.yaml.example
|
64
|
+
- spec/confluence/bookmark_spec.rb
|
65
|
+
- spec/confluence/client_spec.rb
|
64
66
|
- spec/confluence/page_spec.rb
|
67
|
+
- spec/confluence/record_spec.rb
|
65
68
|
- spec/confluence/session_spec.rb
|
66
69
|
- spec/confluencer_spec.rb
|
67
70
|
- spec/spec.opts
|
@@ -97,7 +100,10 @@ signing_key:
|
|
97
100
|
specification_version: 3
|
98
101
|
summary: Useful classes to manage Confluence.
|
99
102
|
test_files:
|
103
|
+
- spec/confluence/bookmark_spec.rb
|
104
|
+
- spec/confluence/client_spec.rb
|
100
105
|
- spec/confluence/page_spec.rb
|
106
|
+
- spec/confluence/record_spec.rb
|
101
107
|
- spec/confluence/session_spec.rb
|
102
108
|
- spec/confluencer_spec.rb
|
103
109
|
- spec/spec_helper.rb
|