about_page 0.1.1 → 0.2.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/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +10 -1
- data/Rakefile +6 -0
- data/app/controllers/about_page/about_controller.rb +5 -10
- data/app/views/about_page/about/health.html.erb +3 -3
- data/lib/about_page/configuration.rb +22 -2
- data/lib/about_page/dependencies.rb +0 -1
- data/lib/about_page/environment.rb +5 -6
- data/lib/about_page/fedora.rb +0 -1
- data/lib/about_page/solr.rb +0 -1
- data/lib/about_page/version.rb +1 -1
- data/lib/tasks/about_page_tasks.rake +19 -4
- data/spec/{integration → features}/about_page_spec.rb +24 -11
- data/spec/features/health_spec.rb +74 -0
- data/spec/lib/solr_spec.rb +4 -5
- metadata +8 -6
- data/Gemfile.lock +0 -140
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -15,24 +15,19 @@ module AboutPage
|
|
15
15
|
def index
|
16
16
|
respond_to do |format|
|
17
17
|
format.html { render :status => @configuration.valid? ? 200 : 417 } # about_page.html.erb
|
18
|
-
format.json { render :json
|
19
|
-
format.xml { render :xml
|
18
|
+
format.json { render :json => @configuration.to_json }
|
19
|
+
format.xml { render :xml => @configuration.to_xml }
|
20
|
+
format.yaml { render :text => @configuration.to_yaml, :content_type => 'text/yaml' }
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
23
24
|
def health
|
24
|
-
@states = @configuration.
|
25
|
-
if profile.class.respond_to?(:validators) and profile.class.validators.length > 0
|
26
|
-
health = profile.valid? ? 'ok' : 'error'
|
27
|
-
{ 'component' => key.to_s, 'status' => health, 'errors' => profile.errors.to_a }
|
28
|
-
else
|
29
|
-
nil
|
30
|
-
end
|
31
|
-
end.compact
|
25
|
+
@states = @configuration.health_report
|
32
26
|
respond_to do |format|
|
33
27
|
format.html { render }
|
34
28
|
format.json { render :json => @states }
|
35
29
|
format.xml { render :xml => @states }
|
30
|
+
format.yaml { render :text => @states.to_yaml, :content_type => 'text/yaml' }
|
36
31
|
end
|
37
32
|
end
|
38
33
|
end
|
@@ -7,9 +7,9 @@
|
|
7
7
|
<span class="label label-<%=component['status'] == 'error' ? 'important' : 'success'%>"><%=component['status']%></span>
|
8
8
|
<% if component['errors'].length > 0 %>
|
9
9
|
<ul>
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
<li class="component-error">
|
11
|
+
<%= component['errors'].to_a.join('</li><li class="component-error">').html_safe %>
|
12
|
+
</li>
|
13
13
|
</ul>
|
14
14
|
<% end %>
|
15
15
|
</li>
|
@@ -27,7 +27,7 @@ module AboutPage
|
|
27
27
|
|
28
28
|
class Configuration
|
29
29
|
attr_accessor :hash
|
30
|
-
delegate :to_xml, :
|
30
|
+
delegate :to_xml, :to_json, :to_yaml, :to => :to_h
|
31
31
|
delegate :each, :map, :to => :to_h
|
32
32
|
|
33
33
|
def initialize hash = nil
|
@@ -55,15 +55,31 @@ module AboutPage
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def nodes
|
58
|
-
self.to_h.select { |key, profile| profile.is_a? AboutPage::Configuration::Node }
|
58
|
+
self.hash.to_h.select { |key, profile| profile.is_a? AboutPage::Configuration::Node }
|
59
59
|
end
|
60
60
|
|
61
61
|
def set_headers! response
|
62
62
|
self.nodes.each { |key, profile| profile.set_headers! response }
|
63
63
|
end
|
64
64
|
|
65
|
+
def to_h
|
66
|
+
self.hash.to_h.inject({}) { |h,v| h[v[0]] = v[1].respond_to?(:to_h) ? v[1].to_h : v[1]; h }
|
67
|
+
end
|
68
|
+
|
69
|
+
def health_report
|
70
|
+
self.nodes.collect do |key, profile|
|
71
|
+
if profile.class.validators.length > 0
|
72
|
+
health = profile.valid? ? 'ok' : 'error'
|
73
|
+
{ 'component' => key.to_s, 'status' => health, 'errors' => profile.errors.to_a }
|
74
|
+
else
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end.compact
|
78
|
+
end
|
79
|
+
|
65
80
|
class Node
|
66
81
|
include ActiveModel::Validations
|
82
|
+
delegate :each_pair, :to_xml, :to_json, :to_yaml, :to => :to_h
|
67
83
|
|
68
84
|
class << self
|
69
85
|
attr_reader :partial
|
@@ -86,6 +102,10 @@ module AboutPage
|
|
86
102
|
errors.to_a.uniq
|
87
103
|
end
|
88
104
|
|
105
|
+
def to_h
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
89
109
|
def set_headers! response
|
90
110
|
messages.each { |m| add_header(response, m) }
|
91
111
|
end
|
@@ -1,17 +1,20 @@
|
|
1
1
|
module AboutPage
|
2
2
|
class Environment < AboutPage::Configuration::Node
|
3
3
|
attr_accessor :sections
|
4
|
-
delegate :each_pair, :to_json, :to_xml, :to => :to_h
|
5
4
|
|
6
5
|
def initialize(sections = {})
|
7
6
|
self.sections = sections
|
8
7
|
end
|
9
8
|
|
9
|
+
def env
|
10
|
+
ENV
|
11
|
+
end
|
12
|
+
|
10
13
|
def to_h
|
11
14
|
@request_env ||= begin
|
12
15
|
h = Hash.new { |h,k| h[k] = {} }
|
13
16
|
|
14
|
-
|
17
|
+
env.each_pair do |key,value|
|
15
18
|
section = section_for(key,value)
|
16
19
|
unless section.nil?
|
17
20
|
h[section][key] = value if value.is_a? String
|
@@ -30,9 +33,5 @@ module AboutPage
|
|
30
33
|
end
|
31
34
|
end
|
32
35
|
end
|
33
|
-
|
34
|
-
def environment
|
35
|
-
ENV
|
36
|
-
end
|
37
36
|
end
|
38
37
|
end
|
data/lib/about_page/fedora.rb
CHANGED
data/lib/about_page/solr.rb
CHANGED
data/lib/about_page/version.rb
CHANGED
@@ -1,4 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
namespace :about do
|
2
|
+
desc "Get a full report in XML or JSON format"
|
3
|
+
task :report, [:format, :section] => [:environment] do |t,args|
|
4
|
+
args.with_defaults(:format => 'yaml')
|
5
|
+
configuration = AboutPage.configuration
|
6
|
+
configuration = configuration.select { |key, value| (args.section.split(/[\W\+]/) + ["app"]).include? key.to_s } if args.section
|
7
|
+
response_method = "to_#{args.format.downcase}".to_sym
|
8
|
+
puts configuration.send(response_method)
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Get a health report in XML or JSON format"
|
12
|
+
task :health, [:format, :section] => [:environment] do |t,args|
|
13
|
+
args.with_defaults(:format => 'yaml')
|
14
|
+
configuration = AboutPage.configuration
|
15
|
+
configuration = configuration.select { |key, value| (args.section.split(/[\W\+]/) + ["app"]).include? key.to_s } if args.section
|
16
|
+
response_method = "to_#{args.format.downcase}".to_sym
|
17
|
+
puts configuration.health_report.send(response_method)
|
18
|
+
end
|
19
|
+
end
|
@@ -4,20 +4,20 @@ describe "the about page", :type => :request do
|
|
4
4
|
before :all do
|
5
5
|
AboutPage.reset!
|
6
6
|
|
7
|
-
AboutPage.configure do |config|
|
8
|
-
|
7
|
+
AboutPage.configure do |config|
|
8
|
+
config.app = { :name => 'Application Name', :version => '0.0.0' }
|
9
9
|
|
10
|
-
|
10
|
+
config.dependencies = AboutPage::Dependencies.new
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
config.environment = AboutPage::Environment.new({
|
13
|
+
'Ruby' => /^(RUBY|GEM_|rvm)/
|
14
|
+
})
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
16
|
+
config.request = AboutPage::RequestEnvironment.new({
|
17
|
+
'HTTP Server' => /^(SERVER_|POW_)/,
|
18
|
+
'WebAuth' => /^WEBAUTH_/
|
19
|
+
})
|
20
|
+
end
|
21
21
|
end
|
22
22
|
describe "dependency versions" do
|
23
23
|
before do
|
@@ -45,6 +45,7 @@ end
|
|
45
45
|
end
|
46
46
|
|
47
47
|
it "should" do
|
48
|
+
page.response_headers['Content-Type'].should =~ /xml/
|
48
49
|
page.body.should_not be_empty
|
49
50
|
end
|
50
51
|
end
|
@@ -55,6 +56,18 @@ end
|
|
55
56
|
end
|
56
57
|
|
57
58
|
it "should" do
|
59
|
+
page.response_headers['Content-Type'].should =~ /json/
|
60
|
+
page.body.should_not be_empty
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "yaml" do
|
65
|
+
before do
|
66
|
+
visit("/about.yaml")
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should" do
|
70
|
+
page.response_headers['Content-Type'].should =~ /yaml/
|
58
71
|
page.body.should_not be_empty
|
59
72
|
end
|
60
73
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "the health page", :type => :request do
|
4
|
+
before :all do
|
5
|
+
module AboutPage
|
6
|
+
class HealthTest < AboutPage::Configuration::Node
|
7
|
+
attr_reader :healthy
|
8
|
+
validates_each :healthy do |r,a,v|
|
9
|
+
r.errors.add a, ": should be healthy" unless v
|
10
|
+
end
|
11
|
+
def initialize(state)
|
12
|
+
@healthy = state
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
after :all do
|
19
|
+
AboutPage.send(:remove_const, :HealthTest)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "report" do
|
23
|
+
before do
|
24
|
+
AboutPage.reset!
|
25
|
+
AboutPage.configure do |config|
|
26
|
+
config.yup = AboutPage::HealthTest.new(true)
|
27
|
+
config.nope = AboutPage::HealthTest.new(false)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "html" do
|
32
|
+
before do
|
33
|
+
visit('/about/health')
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "healthy" do
|
37
|
+
before do
|
38
|
+
@context = page.find('li[class=component][1]')
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should report the service name" do
|
42
|
+
@context.should have_content('yup')
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should contain an ok status" do
|
46
|
+
@context.should have_xpath('span[@class="label label-success"][text() = "ok"]')
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should not contain an error list" do
|
50
|
+
@context.should_not have_xpath('ul/li[@class="component-error"]')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "unhealthy" do
|
55
|
+
before do
|
56
|
+
@context = page.find('li[class=component][2]')
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should report the service name" do
|
60
|
+
@context.should have_content('nope')
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should contain an error status" do
|
64
|
+
@context.should have_xpath('span[@class="label label-important"][text() = "error"]')
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should contain an error list" do
|
68
|
+
@context.should have_xpath('ul/li[@class="component-error"]')
|
69
|
+
@context.should have_content('Healthy : should be healthy')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/spec/lib/solr_spec.rb
CHANGED
@@ -2,8 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe AboutPage::Solr do
|
4
4
|
before :each do
|
5
|
-
@mock_solr_connection = double('RSolr::Connection')
|
6
|
-
@mock_solr_connection.stub(:uri, URI.parse("http://example.edu/solr"))
|
5
|
+
@mock_solr_connection = double('RSolr::Connection', :uri => URI.parse("http://example.edu/solr"))
|
7
6
|
end
|
8
7
|
|
9
8
|
subject { AboutPage::Solr.new(@mock_solr_connection) }
|
@@ -21,7 +20,7 @@ describe AboutPage::Solr do
|
|
21
20
|
|
22
21
|
describe "#index" do
|
23
22
|
it "should get the index information from the schema" do
|
24
|
-
m =
|
23
|
+
m = double()
|
25
24
|
subject.stub(:schema).and_return { { 'index' => m }}
|
26
25
|
|
27
26
|
subject.index.should == m
|
@@ -65,7 +64,7 @@ describe AboutPage::Solr do
|
|
65
64
|
subject.stub(:expects).and_return 5
|
66
65
|
|
67
66
|
subject.should_receive(:add_header)
|
68
|
-
subject.set_headers!
|
67
|
+
subject.set_headers! double()
|
69
68
|
end
|
70
69
|
end
|
71
70
|
|
@@ -81,7 +80,7 @@ describe AboutPage::Solr do
|
|
81
80
|
|
82
81
|
it "should use the request parameters to set the minimum_numdocs" do
|
83
82
|
node = AboutPage::Solr.new(@mock_solr_connection, :expects => { :numDocs => 5 })
|
84
|
-
node.preflight(
|
83
|
+
node.preflight(double(:params => { 'solr.numDocs' => 1000 }))
|
85
84
|
|
86
85
|
node.expects(:numDocs).should == 1000
|
87
86
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: about_page
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -102,8 +102,8 @@ extra_rdoc_files: []
|
|
102
102
|
files:
|
103
103
|
- .gitignore
|
104
104
|
- .rspec
|
105
|
+
- .travis.yml
|
105
106
|
- Gemfile
|
106
|
-
- Gemfile.lock
|
107
107
|
- LICENSE
|
108
108
|
- README.md
|
109
109
|
- Rakefile
|
@@ -168,7 +168,8 @@ files:
|
|
168
168
|
- spec/dummy/public/favicon.ico
|
169
169
|
- spec/dummy/script/rails
|
170
170
|
- spec/dummy/tmp/capybara/capybara-201204200939394978936149.html
|
171
|
-
- spec/
|
171
|
+
- spec/features/about_page_spec.rb
|
172
|
+
- spec/features/health_spec.rb
|
172
173
|
- spec/lib/about_page_configuration.rb
|
173
174
|
- spec/lib/configuration_environment_spec.rb
|
174
175
|
- spec/lib/solr_spec.rb
|
@@ -187,7 +188,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
187
188
|
version: '0'
|
188
189
|
segments:
|
189
190
|
- 0
|
190
|
-
hash:
|
191
|
+
hash: 2575242270750158718
|
191
192
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
192
193
|
none: false
|
193
194
|
requirements:
|
@@ -196,7 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
196
197
|
version: '0'
|
197
198
|
segments:
|
198
199
|
- 0
|
199
|
-
hash:
|
200
|
+
hash: 2575242270750158718
|
200
201
|
requirements: []
|
201
202
|
rubyforge_project:
|
202
203
|
rubygems_version: 1.8.23
|
@@ -239,7 +240,8 @@ test_files:
|
|
239
240
|
- spec/dummy/public/favicon.ico
|
240
241
|
- spec/dummy/script/rails
|
241
242
|
- spec/dummy/tmp/capybara/capybara-201204200939394978936149.html
|
242
|
-
- spec/
|
243
|
+
- spec/features/about_page_spec.rb
|
244
|
+
- spec/features/health_spec.rb
|
243
245
|
- spec/lib/about_page_configuration.rb
|
244
246
|
- spec/lib/configuration_environment_spec.rb
|
245
247
|
- spec/lib/solr_spec.rb
|
data/Gemfile.lock
DELETED
@@ -1,140 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
about_page (0.1.1)
|
5
|
-
rails (~> 3.2)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: http://rubygems.org/
|
9
|
-
specs:
|
10
|
-
actionmailer (3.2.3)
|
11
|
-
actionpack (= 3.2.3)
|
12
|
-
mail (~> 2.4.4)
|
13
|
-
actionpack (3.2.3)
|
14
|
-
activemodel (= 3.2.3)
|
15
|
-
activesupport (= 3.2.3)
|
16
|
-
builder (~> 3.0.0)
|
17
|
-
erubis (~> 2.7.0)
|
18
|
-
journey (~> 1.0.1)
|
19
|
-
rack (~> 1.4.0)
|
20
|
-
rack-cache (~> 1.2)
|
21
|
-
rack-test (~> 0.6.1)
|
22
|
-
sprockets (~> 2.1.2)
|
23
|
-
activemodel (3.2.3)
|
24
|
-
activesupport (= 3.2.3)
|
25
|
-
builder (~> 3.0.0)
|
26
|
-
activerecord (3.2.3)
|
27
|
-
activemodel (= 3.2.3)
|
28
|
-
activesupport (= 3.2.3)
|
29
|
-
arel (~> 3.0.2)
|
30
|
-
tzinfo (~> 0.3.29)
|
31
|
-
activeresource (3.2.3)
|
32
|
-
activemodel (= 3.2.3)
|
33
|
-
activesupport (= 3.2.3)
|
34
|
-
activesupport (3.2.3)
|
35
|
-
i18n (~> 0.6)
|
36
|
-
multi_json (~> 1.0)
|
37
|
-
addressable (2.2.7)
|
38
|
-
arel (3.0.2)
|
39
|
-
builder (3.0.0)
|
40
|
-
capybara (1.1.2)
|
41
|
-
mime-types (>= 1.16)
|
42
|
-
nokogiri (>= 1.3.3)
|
43
|
-
rack (>= 1.0.0)
|
44
|
-
rack-test (>= 0.5.4)
|
45
|
-
selenium-webdriver (~> 2.0)
|
46
|
-
xpath (~> 0.1.4)
|
47
|
-
childprocess (0.3.2)
|
48
|
-
ffi (~> 1.0.6)
|
49
|
-
diff-lcs (1.1.3)
|
50
|
-
erubis (2.7.0)
|
51
|
-
ffi (1.0.11)
|
52
|
-
hike (1.2.1)
|
53
|
-
i18n (0.6.0)
|
54
|
-
journey (1.0.3)
|
55
|
-
jquery-rails (2.0.2)
|
56
|
-
railties (>= 3.2.0, < 5.0)
|
57
|
-
thor (~> 0.14)
|
58
|
-
json (1.6.6)
|
59
|
-
launchy (2.1.0)
|
60
|
-
addressable (~> 2.2.6)
|
61
|
-
libwebsocket (0.1.3)
|
62
|
-
addressable
|
63
|
-
mail (2.4.4)
|
64
|
-
i18n (>= 0.4.0)
|
65
|
-
mime-types (~> 1.16)
|
66
|
-
treetop (~> 1.4.8)
|
67
|
-
mime-types (1.18)
|
68
|
-
multi_json (1.3.2)
|
69
|
-
nokogiri (1.5.2)
|
70
|
-
polyglot (0.3.3)
|
71
|
-
rack (1.4.1)
|
72
|
-
rack-cache (1.2)
|
73
|
-
rack (>= 0.4)
|
74
|
-
rack-ssl (1.3.2)
|
75
|
-
rack
|
76
|
-
rack-test (0.6.1)
|
77
|
-
rack (>= 1.0)
|
78
|
-
rails (3.2.3)
|
79
|
-
actionmailer (= 3.2.3)
|
80
|
-
actionpack (= 3.2.3)
|
81
|
-
activerecord (= 3.2.3)
|
82
|
-
activeresource (= 3.2.3)
|
83
|
-
activesupport (= 3.2.3)
|
84
|
-
bundler (~> 1.0)
|
85
|
-
railties (= 3.2.3)
|
86
|
-
railties (3.2.3)
|
87
|
-
actionpack (= 3.2.3)
|
88
|
-
activesupport (= 3.2.3)
|
89
|
-
rack-ssl (~> 1.3.2)
|
90
|
-
rake (>= 0.8.7)
|
91
|
-
rdoc (~> 3.4)
|
92
|
-
thor (~> 0.14.6)
|
93
|
-
rake (0.9.2.2)
|
94
|
-
rdoc (3.12)
|
95
|
-
json (~> 1.4)
|
96
|
-
rspec (2.9.0)
|
97
|
-
rspec-core (~> 2.9.0)
|
98
|
-
rspec-expectations (~> 2.9.0)
|
99
|
-
rspec-mocks (~> 2.9.0)
|
100
|
-
rspec-core (2.9.0)
|
101
|
-
rspec-expectations (2.9.1)
|
102
|
-
diff-lcs (~> 1.1.3)
|
103
|
-
rspec-mocks (2.9.0)
|
104
|
-
rspec-rails (2.9.0)
|
105
|
-
actionpack (>= 3.0)
|
106
|
-
activesupport (>= 3.0)
|
107
|
-
railties (>= 3.0)
|
108
|
-
rspec (~> 2.9.0)
|
109
|
-
rubyzip (0.9.7)
|
110
|
-
selenium-webdriver (2.21.2)
|
111
|
-
childprocess (>= 0.2.5)
|
112
|
-
ffi (~> 1.0)
|
113
|
-
libwebsocket (~> 0.1.3)
|
114
|
-
multi_json (~> 1.0)
|
115
|
-
rubyzip
|
116
|
-
sprockets (2.1.2)
|
117
|
-
hike (~> 1.2)
|
118
|
-
rack (~> 1.0)
|
119
|
-
tilt (~> 1.1, != 1.3.0)
|
120
|
-
sqlite3 (1.3.6)
|
121
|
-
thor (0.14.6)
|
122
|
-
tilt (1.3.3)
|
123
|
-
treetop (1.4.14)
|
124
|
-
polyglot
|
125
|
-
polyglot (>= 0.3.1)
|
126
|
-
tzinfo (0.3.37)
|
127
|
-
xpath (0.1.4)
|
128
|
-
nokogiri (~> 1.3)
|
129
|
-
|
130
|
-
PLATFORMS
|
131
|
-
ruby
|
132
|
-
|
133
|
-
DEPENDENCIES
|
134
|
-
about_page!
|
135
|
-
capybara
|
136
|
-
jquery-rails
|
137
|
-
launchy
|
138
|
-
rspec
|
139
|
-
rspec-rails
|
140
|
-
sqlite3
|