rails-caddy 0.0.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/LICENSE +20 -0
- data/README.rdoc +69 -0
- data/VERSION.yml +4 -0
- data/lib/rails-caddy.rb +43 -0
- data/lib/rails-caddy/controllers/rails_caddy_controller.rb +17 -0
- data/lib/rails-caddy/controllers/sanitize_email_controller.rb +42 -0
- data/lib/rails-caddy/controllers/session_editing_controller.rb +21 -0
- data/lib/rails-caddy/controllers/timecop_controller.rb +56 -0
- data/lib/rails-caddy/errors.rb +8 -0
- data/lib/rails-caddy/helpers/rails_caddy_helper.rb +33 -0
- data/lib/rails-caddy/session_controller_finder.rb +17 -0
- data/lib/rails-caddy/views/_rails_caddy.html.erb +57 -0
- data/lib/rails-caddy/views/_rails_caddy_css.html.erb +60 -0
- data/lib/rails-caddy/views/_rails_caddy_js.html.erb +88 -0
- data/test/README.txt +40 -0
- data/test/files/acet.rb +40 -0
- data/test/files/fct.rb +35 -0
- data/test/files/rcct.rb +13 -0
- data/test/files/sanitize_email_action_controller_extensions_test_methods.rb +71 -0
- data/test/files/sanitize_email_controller_test_methods.rb +31 -0
- data/test/files/session_editing_controller_test_methods.rb +41 -0
- data/test/files/timecop_action_controller_extensions_test_methods.rb +55 -0
- data/test/files/timecop_controller_test_methods.rb +30 -0
- data/test/geminstaller.yml +5 -0
- data/test/rails_caddy_controller_test.rb +43 -0
- data/test/rails_caddy_helper_test.rb +24 -0
- data/test/rails_caddy_test.rb +39 -0
- data/test/rails_modifier.rb +131 -0
- data/test/routes.rb +3 -0
- data/test/session_controller_finder_test.rb +57 -0
- data/test/test_helper.rb +12 -0
- metadata +110 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 John Trupiano
|
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.rdoc
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
= rails-caddy
|
2
|
+
|
3
|
+
A developer's QA "caddy" that aids in QA'ing, debugging, and otherwise navigating your application during development and/or QA.
|
4
|
+
|
5
|
+
== Rails Compatibility
|
6
|
+
|
7
|
+
Tests cover rails 2.1.2, 2.2.2, and 2.3.2 specifically. Previous releases on each minor version have not been explicitly tested, but I suspect they should all function equally well.
|
8
|
+
|
9
|
+
== Usage
|
10
|
+
|
11
|
+
RULE #1: DO NOT DEPLOY THIS TO PRODUCTION. I WILL HAVE NO EMPATHY WHATSOEVER IF YOU DO NOT HEED MY WARNING. THIS IS A VERY DANGEROUS GEM THAT WILL DEFINITELY SCREW YOU OVER IF YOU DEPLOY IT TO PRODUCTION.
|
12
|
+
|
13
|
+
This process is not currently scripted, but following these steps will get you off and running.
|
14
|
+
|
15
|
+
* edit application.rb
|
16
|
+
|
17
|
+
if Object.const_defined?(:RailsCaddy)
|
18
|
+
helper RailsCaddyHelper
|
19
|
+
around_filter :handle_sanitize_email
|
20
|
+
around_filter :handle_timecop_offset, :except => [:timecop_update, :timecop_reset]
|
21
|
+
end
|
22
|
+
|
23
|
+
* edit config/environments/development.rb -- DO NOT MAKE THIS AVAILABLE TO PRODUCTION!!!
|
24
|
+
|
25
|
+
config.gem "rails-caddy"
|
26
|
+
|
27
|
+
config.after_initialize do
|
28
|
+
require 'rails-caddy'
|
29
|
+
require_dependency 'application_controller' # 'application' if pre rails 2.3
|
30
|
+
RailsCaddy.init!
|
31
|
+
|
32
|
+
ActionMailer::Base.sanitized_recipients = "nobody@smartlogicsolutions.com"
|
33
|
+
end
|
34
|
+
|
35
|
+
* add just before you close your body tag in your layout (it's actually unimportant where you place it, as long as it's in the body):
|
36
|
+
|
37
|
+
<%= rails_caddy if Object.const_defined?(:RailsCaddy) %>
|
38
|
+
|
39
|
+
* add to the top of config/routes.rb
|
40
|
+
|
41
|
+
RailsCaddy.define_routes!(map) if Object.const_defined?(:RailsCaddy)
|
42
|
+
|
43
|
+
== Dependencies
|
44
|
+
|
45
|
+
rails-caddy is dependent on the {sanitize_email gem}[http://github.com/jtrupiano/sanitize_email/tree/master]. Unfortunately, for the time being you'll need to build and install that locally. Why? Because the dependency is on +sanitize_email+ and NOT <tt>jtrupiano-sanitize_email</tt>, the latter of which can be installed remotely. Hopefully this will change soon (that's _your_ cue to fork and fix).
|
46
|
+
|
47
|
+
== Building/Testing
|
48
|
+
|
49
|
+
In order to run the tests, you'll want to build the gem. Why? Because <tt>rake test:rails_compatibility</tt> tests all supported versions of rails (see <b>Rails Compatibility</b>, above). It does this by creating mini-Rails apps for each version and config-gem'ing rails-caddy (<tt>config.gem 'rails-caddy'</tt>). If you don't build the gem and try to run the tests, you'll get an error telling you to <tt>run `rake gems:install`</tt>.
|
50
|
+
|
51
|
+
The easiest way to build the gem is to install {technicalpickle's}[http://technicalpickles.com/] {jeweler gem}[http://github.com/technicalpickles/jeweler/tree/master]: <tt>sudo gem install jeweler</tt>.
|
52
|
+
|
53
|
+
After jeweler is installed, you can build and install with some handy rake tasks:
|
54
|
+
|
55
|
+
rake build
|
56
|
+
rake install
|
57
|
+
|
58
|
+
<b>n.b.</b> <tt>rake install</tt> uses +sudo+
|
59
|
+
|
60
|
+
(Problems? See *Dependencies*, above.)
|
61
|
+
|
62
|
+
Now you can run your tests:
|
63
|
+
|
64
|
+
rake test:all # Run all test suites.
|
65
|
+
rake test:rails_compatibility # Test all supported versions of rails.
|
66
|
+
|
67
|
+
== Copyright
|
68
|
+
|
69
|
+
Copyright (c) 2009 John Trupiano. See LICENSE for details.
|
data/VERSION.yml
ADDED
data/lib/rails-caddy.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'timecop'
|
2
|
+
require 'actionmailer' # would like to remove this
|
3
|
+
require 'sanitize_email'
|
4
|
+
require 'rails-caddy/errors'
|
5
|
+
require 'rails-caddy/controllers/session_editing_controller'
|
6
|
+
require 'rails-caddy/controllers/timecop_controller'
|
7
|
+
require 'rails-caddy/controllers/sanitize_email_controller'
|
8
|
+
require 'rails-caddy/helpers/rails_caddy_helper'
|
9
|
+
|
10
|
+
$rails_caddy_activated = false
|
11
|
+
|
12
|
+
class RailsCaddy
|
13
|
+
|
14
|
+
def self.init!
|
15
|
+
# extend ActionController::Base
|
16
|
+
ActionController::Base.send(:include, TimecopController::ActionControllerExtensions)
|
17
|
+
ActionController::Base.send(:include, SanitizeEmailController::ActionControllerExtensions)
|
18
|
+
|
19
|
+
# Pull in the RailsCaddyController
|
20
|
+
require 'rails-caddy/controllers/rails_caddy_controller'
|
21
|
+
|
22
|
+
# Lastly, let's add our views to the load path...
|
23
|
+
ActionController::Base.append_view_path(File.expand_path(File.join(File.dirname(__FILE__), "rails-caddy", "views")))
|
24
|
+
|
25
|
+
# we will inspect this at a few places in the consuming rails app, most notably config/routes.rb
|
26
|
+
$rails_caddy_activated = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.define_routes!(map)
|
30
|
+
# Session editing routes
|
31
|
+
map.update_session '/rails_caddy/update_session/:id', :controller => 'rails_caddy', :action => 'update_session'
|
32
|
+
map.remove_session '/rails_caddy/remove_session/:id', :controller => 'rails_caddy', :action => 'remove_session'
|
33
|
+
|
34
|
+
# Timecop routes
|
35
|
+
map.timecop_update '/rails_caddy/timecop_update', :controller => 'rails_caddy', :action => 'timecop_update'
|
36
|
+
map.timecop_reset '/rails_caddy/timecop_reset', :controller => 'rails_caddy', :action => 'timecop_reset'
|
37
|
+
|
38
|
+
# Sanitize Email routes
|
39
|
+
map.set_sanitize_email_address '/rails_caddy/set_sanitize_email_address', :controller => 'rails_caddy', :action => 'set_sanitize_email_address'
|
40
|
+
map.unset_sanitize_email_address '/rails_caddy/unset_sanitize_email_address', :controller => 'rails_caddy', :action => 'unset_sanitize_email_address'
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rails-caddy/session_controller_finder'
|
2
|
+
|
3
|
+
# Find the controller responsible for establishing session
|
4
|
+
session_controller = SessionControllerFinder.find
|
5
|
+
|
6
|
+
# Dynamically define the RailsCaddyController now
|
7
|
+
c = Class.new(session_controller) do
|
8
|
+
include SessionEditingController
|
9
|
+
include TimecopController
|
10
|
+
include SanitizeEmailController
|
11
|
+
|
12
|
+
def verify_authenticity_token
|
13
|
+
# this is lame that I have to override it here...can't figure out why a skip_before_filter fails
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
Object.const_set("RailsCaddyController", c)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SanitizeEmailController
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
base.send(:include, Actions)
|
5
|
+
end
|
6
|
+
|
7
|
+
module Actions
|
8
|
+
|
9
|
+
def set_sanitize_email_address
|
10
|
+
session[:sanitize_email_address] = params[:value]
|
11
|
+
render :status => 200, :text => params[:value]
|
12
|
+
end
|
13
|
+
|
14
|
+
def unset_sanitize_email_address
|
15
|
+
session[:sanitize_email_address] = nil
|
16
|
+
render :status => 200, :text => "nil"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ActionControllerExtensions
|
21
|
+
# def self.included(base)
|
22
|
+
# base.class_eval do
|
23
|
+
# around_filter :handle_sanitize_email
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
|
27
|
+
def handle_sanitize_email
|
28
|
+
if session[:sanitize_email_address].nil?
|
29
|
+
yield
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
@original_sanitized_recipients = ActionMailer::Base.sanitized_recipients
|
34
|
+
ActionMailer::Base.sanitized_recipients = session[:sanitize_email_address]
|
35
|
+
yield
|
36
|
+
ensure
|
37
|
+
ActionMailer::Base.sanitized_recipients = @original_sanitized_recipients
|
38
|
+
end
|
39
|
+
|
40
|
+
private :handle_sanitize_email
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SessionEditingController
|
2
|
+
|
3
|
+
def update_session
|
4
|
+
if params[:id].nil?
|
5
|
+
render :status => 422, :text => "Invalid request. No session variable provided."
|
6
|
+
return false
|
7
|
+
end
|
8
|
+
session[params[:id].to_sym] = params[:value]
|
9
|
+
render :status => 200, :text => params[:value]
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove_session
|
13
|
+
if params[:id].nil?
|
14
|
+
render :status => 422, :text => "Invalid request. Session variable is either missing or invalid."
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
session[params[:id].to_sym] = nil
|
18
|
+
render :status => 200, :nothing => true
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module TimecopController
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
# base.class_eval do
|
5
|
+
# # Make sure we don't reset our own time!
|
6
|
+
# skip_filter :handle_timecop_offset
|
7
|
+
# end
|
8
|
+
base.send(:include, Actions)
|
9
|
+
end
|
10
|
+
|
11
|
+
module Actions
|
12
|
+
def timecop_update
|
13
|
+
year, month, day, hour, min, sec = params[:year], params[:month], params[:day], params[:hour], params[:min], params[:sec]
|
14
|
+
session[:timecop_adjusted_time] = Time.local(year, month, day, hour, min, sec)
|
15
|
+
render :status => 200, :nothing => true
|
16
|
+
end
|
17
|
+
|
18
|
+
def timecop_reset
|
19
|
+
session[:timecop_adjusted_time] = nil
|
20
|
+
render :status => 200, :nothing => true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ActionControllerExtensions
|
25
|
+
# def self.included(base)
|
26
|
+
# base.class_eval do
|
27
|
+
# around_filter :handle_timecop_offset
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
|
31
|
+
# to be used as an around_filter
|
32
|
+
def handle_timecop_offset
|
33
|
+
# Establish now
|
34
|
+
if !session[:timecop_adjusted_time].nil?
|
35
|
+
#puts "***** Time traveling to #{session[:timecop_adjusted_time].to_s}"
|
36
|
+
Timecop.travel(session[:timecop_adjusted_time])
|
37
|
+
else
|
38
|
+
Timecop.return
|
39
|
+
end
|
40
|
+
|
41
|
+
# Run the intended action
|
42
|
+
yield
|
43
|
+
|
44
|
+
# we want to continue to slide time forward, even if it's only 3 seconds at a time.
|
45
|
+
# this ensures that subsequent calls during the same "time travel" actually pass time
|
46
|
+
if !session[:timecop_adjusted_time].nil?
|
47
|
+
#puts "====== Resetting session to: #{Time.now + 3}"
|
48
|
+
session[:timecop_adjusted_time] = Time.now + 3 # slide it forward a couple of seconds
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private :handle_timecop_offset
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module RailsCaddyHelper
|
2
|
+
# actually embeds the magic that is the Rails Caddy
|
3
|
+
def rails_caddy
|
4
|
+
render(:partial => '/rails_caddy')
|
5
|
+
end
|
6
|
+
|
7
|
+
def translated_remove_session_path
|
8
|
+
url = remove_session_path
|
9
|
+
url << "/" unless url.match(/\/$/)
|
10
|
+
end
|
11
|
+
|
12
|
+
def rc_in_place_editor(key, field_id, url)
|
13
|
+
function = "RailsCaddy.editors['" + key + "'] = new Ajax.InPlaceEditor("
|
14
|
+
function << "'#{field_id}', "
|
15
|
+
function << "'#{url}'"
|
16
|
+
function << ');'
|
17
|
+
|
18
|
+
javascript_tag(function)
|
19
|
+
end
|
20
|
+
|
21
|
+
def rc_in_place_editor_field(key, value)
|
22
|
+
tag = ::ActionView::Helpers::InstanceTag.new("rails_caddy", key, self)
|
23
|
+
tag_options = {:tag => "span", :id => "rails_caddy_#{key}_in_place_editor", :name => "value", :class => "in_place_editor_field"}
|
24
|
+
|
25
|
+
# rails < 2.3 needs to be treated with kid gloves.
|
26
|
+
url = update_session_path
|
27
|
+
url << "/" unless url.match(/\/$/)
|
28
|
+
url << key
|
29
|
+
|
30
|
+
tag.content_tag(tag_options.delete(:tag), value, tag_options) + rc_in_place_editor(key, tag_options[:id], url)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Responsible for locating the controller responsible for establishing the session
|
2
|
+
class SessionControllerFinder
|
3
|
+
def self.find
|
4
|
+
if !Object.const_defined?(:ApplicationController)
|
5
|
+
raise RailsCaddy::SessionControllerNotFoundError,
|
6
|
+
"Cannot find ApplicationController. If you're sure that you have defined it, try adding require_dependency 'application_controller' prior to invoking RailsCaddy.init!"
|
7
|
+
end
|
8
|
+
|
9
|
+
candidate = ApplicationController
|
10
|
+
# if candidate.session_options[:key].nil?
|
11
|
+
# raise RailsCaddy::SessionUninitializedError,
|
12
|
+
# "session does not appear to be established for #{candidate.class}. session: #{candidate.session_options.inspect}"
|
13
|
+
# end
|
14
|
+
|
15
|
+
candidate
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
<%= render(:partial => '/rails_caddy_css') %>
|
2
|
+
|
3
|
+
<%= render(:partial => '/rails_caddy_js')%>
|
4
|
+
|
5
|
+
<div id="railsCaddy">
|
6
|
+
|
7
|
+
<a href="#" id="railsCaddyTab">caddy</a>
|
8
|
+
|
9
|
+
<div id="railsCaddyContents" style="display: none;">
|
10
|
+
<div id="railsCaddyContentsInner">
|
11
|
+
<div id="railsCaddySession">
|
12
|
+
<h2>Session</h2>
|
13
|
+
<div id="sessionObjects">
|
14
|
+
<script type="text/javascript">
|
15
|
+
<% (session.data.keys - ["flash", :timecop_adjusted_time, :sanitize_email_address]).each do |key| -%>
|
16
|
+
<% next if session[key].nil? -%>
|
17
|
+
document.write(RailsCaddy.sessionVariableEditor("<%= escape_javascript(key.to_s) %>", "<%= escape_javascript(session[key].to_s) %>"));
|
18
|
+
<% end -%>
|
19
|
+
</script>
|
20
|
+
</div>
|
21
|
+
<p><a href="javascript:void(0)" onclick="RailsCaddy.addSessionVariable();">+ New Session Variable</a></p>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div id="railsCaddyTimecop">
|
25
|
+
<h2>Timecop</h2>
|
26
|
+
<p>The time is <strong style="color: red;"><%= Time.now.to_s(:db) %></strong></p>
|
27
|
+
<% form_remote_tag :url => timecop_update_path do -%>
|
28
|
+
<% %w(year month day hour min sec).each do |field| -%>
|
29
|
+
<%= text_field_tag field, Time.now.send(field), :size => (field == "year" ? 3 : 1) %>
|
30
|
+
<% end -%>
|
31
|
+
<p>
|
32
|
+
<%= submit_tag "Time Travel" %>
|
33
|
+
<%= link_to_remote "Reset", :url => timecop_reset_path %>
|
34
|
+
</p>
|
35
|
+
<% end -%>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
<div id="railsCaddyEmail">
|
39
|
+
<h2>Sanitize Email</h2>
|
40
|
+
<p>All email sent to: <strong style="color: red;"><%= ActionMailer::Base.sanitized_recipients %></strong></p>
|
41
|
+
<% form_remote_tag :url => set_sanitize_email_address_path do -%>
|
42
|
+
<%= text_field_tag "value", "", :size => 15 %>
|
43
|
+
<p>
|
44
|
+
<%= submit_tag "Change Email" %>
|
45
|
+
<%= link_to_remote "Unset", :url => unset_sanitize_email_address_path %>
|
46
|
+
</p>
|
47
|
+
<% end -%>
|
48
|
+
</div>
|
49
|
+
|
50
|
+
<div id="railsCaddyStats">
|
51
|
+
<h2>Stats</h2>
|
52
|
+
</div>
|
53
|
+
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
|
57
|
+
</div>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
<style type="text/css">
|
2
|
+
#railsCaddy {
|
3
|
+
text-align: left;
|
4
|
+
position: absolute;
|
5
|
+
width: auto;
|
6
|
+
height: auto;
|
7
|
+
top: 100px;
|
8
|
+
left: 0px;
|
9
|
+
z-index: 100;
|
10
|
+
font-family: Helvetica, Arial, Times;
|
11
|
+
}
|
12
|
+
|
13
|
+
#railsCaddyTab {
|
14
|
+
float: left;
|
15
|
+
height: 137px;
|
16
|
+
width: 28px;
|
17
|
+
text-decoration: none;
|
18
|
+
}
|
19
|
+
|
20
|
+
#railsCaddyTab img {
|
21
|
+
border: none;
|
22
|
+
}
|
23
|
+
|
24
|
+
#railsCaddyContents {
|
25
|
+
float: left;
|
26
|
+
overflow: hidden !important;
|
27
|
+
width: 200px;
|
28
|
+
}
|
29
|
+
|
30
|
+
#railsCaddyContentsInner {
|
31
|
+
width: 200px;
|
32
|
+
margin-top: 30px;
|
33
|
+
font-size: 12px;
|
34
|
+
}
|
35
|
+
|
36
|
+
#railsCaddyContentsInner > div {
|
37
|
+
border: solid 1px red;
|
38
|
+
}
|
39
|
+
|
40
|
+
#railsCaddyContentsInner h2 {
|
41
|
+
margin: 0px 0px 3px 0px;
|
42
|
+
padding: 2px;
|
43
|
+
background-color: #CC3400;
|
44
|
+
color: #FFFFFF;
|
45
|
+
font-size: 14px;
|
46
|
+
}
|
47
|
+
|
48
|
+
#railsCaddySession p {
|
49
|
+
margin-top: 0px;
|
50
|
+
margin-bottom: 4px;
|
51
|
+
}
|
52
|
+
|
53
|
+
#railsCaddySession p strong {
|
54
|
+
font-size: 110%;
|
55
|
+
}
|
56
|
+
|
57
|
+
#railsCaddySession a.x {
|
58
|
+
color: red;
|
59
|
+
}
|
60
|
+
</style>
|