active-record-profiler 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +150 -0
- data/Rakefile +14 -0
- data/active-record-profiler.gemspec +30 -0
- data/lib/active-record-profiler.rb +13 -0
- data/lib/active-record-profiler/collector.rb +276 -0
- data/lib/active-record-profiler/log_subscriber.rb +30 -0
- data/lib/active-record-profiler/logger.rb +67 -0
- data/lib/active-record-profiler/profiler_view_helper.rb +200 -0
- data/lib/active-record-profiler/railtie.rb +18 -0
- data/lib/active-record-profiler/tasks.rake +41 -0
- data/lib/active-record-profiler/version.rb +3 -0
- data/test/active_record_profiler_test.rb +29 -0
- data/test/database.yml +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/controllers/profiler_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.keep +0 -0
- data/test/dummy/app/models/.keep +0 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/models/widget.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/profiler/index.html.erb +4 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +25 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/active_record_profiler.rb +3 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/db/migrate/20150109175941_create_widgets.rb +8 -0
- data/test/dummy/db/schema.rb +21 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/test/fixtures/widgets.yml +9 -0
- data/test/dummy/test/models/widget_test.rb +7 -0
- data/test/schema.rb +7 -0
- data/test/test_helper.rb +24 -0
- metadata +237 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_record/log_subscriber'
|
2
|
+
|
3
|
+
module ActiveRecordProfiler
|
4
|
+
class LogSubscriber < ActiveRecord::LogSubscriber
|
5
|
+
def sql(event)
|
6
|
+
start_time = Time.now.to_f
|
7
|
+
payload = event.payload
|
8
|
+
|
9
|
+
duration = event.duration
|
10
|
+
sql_string = payload[:sql]
|
11
|
+
|
12
|
+
begin
|
13
|
+
collector = ActiveRecordProfiler::Collector.instance
|
14
|
+
loc = collector.call_location_name
|
15
|
+
collector.record_caller_info(loc, duration, sql_string.strip)
|
16
|
+
|
17
|
+
collector.record_self_info((Time.now.to_f - start_time), 'updating profiler stats') if ActiveRecordProfiler::Collector.profile_self?
|
18
|
+
|
19
|
+
start_time = Time.now.to_f
|
20
|
+
if collector.should_flush_stats?
|
21
|
+
collector.flush_query_sites_statistics
|
22
|
+
collector.record_self_info((Time.now.to_f - start_time), 'flushing profiler stats') if ActiveRecordProfiler::Collector.profile_self?
|
23
|
+
end
|
24
|
+
rescue Exception => e
|
25
|
+
Rails.logger.error("Caught exception in #{self.class}: #{e} at #{e.backtrace.first}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module ActiveRecordProfiler
|
4
|
+
class Logger < SimpleDelegator
|
5
|
+
|
6
|
+
def add(severity, message = nil, progname = nil, &block)
|
7
|
+
return true if (severity || ::Logger::Severity::UNKNOWN) < self.level
|
8
|
+
|
9
|
+
if message.nil?
|
10
|
+
if block_given?
|
11
|
+
message = yield
|
12
|
+
else
|
13
|
+
message = progname
|
14
|
+
progname = self.progname
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
message = add_call_site_to_message(message)
|
19
|
+
|
20
|
+
super(severity, message, progname, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Define all of the basic logging methods so that they invoke our add()
|
24
|
+
# method rather than delegating to the delagatee's methods, which will then
|
25
|
+
# invoke the delegatee's add() method that does not add call-site
|
26
|
+
# information.
|
27
|
+
[:debug, :info, :warn, :error, :fatal, :unknown].each do |level|
|
28
|
+
define_method(level) do |progname = nil, &block|
|
29
|
+
add(
|
30
|
+
::Logger::Severity.const_get(level.to_s.upcase),
|
31
|
+
nil,
|
32
|
+
progname,
|
33
|
+
&block
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def add_call_site_to_message(msg)
|
40
|
+
message = msg
|
41
|
+
|
42
|
+
if String === msg
|
43
|
+
match = /^\s*(?:\e\[\d+m)*(:?[^(]*)\(([0-9.]+)\s*([a-z]s(:?econds)?)\)(?:\e\[\d+m)*\s*(.*)/.match(msg)
|
44
|
+
|
45
|
+
if match
|
46
|
+
loc = collector.call_location_name
|
47
|
+
message = "#{msg} CALLED BY '#{formatted_location(loc)}'"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
return message
|
52
|
+
end
|
53
|
+
|
54
|
+
def collector
|
55
|
+
ActiveRecordProfiler::Collector.instance
|
56
|
+
end
|
57
|
+
|
58
|
+
def formatted_location(loc)
|
59
|
+
if Rails.configuration.colorize_logging
|
60
|
+
"\e[4;32;1m#{loc}\e[0m"
|
61
|
+
else
|
62
|
+
loc
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
module ActiveRecordProfiler
|
2
|
+
module ProfilerViewHelper
|
3
|
+
def profiler_date_filter_form(page_params, options = {})
|
4
|
+
page_params ||= {}
|
5
|
+
options ||= {}
|
6
|
+
date = options[:date] || page_params[:date] || Time.now.strftime(ActiveRecordProfiler::Collector::DATE_FORMAT)
|
7
|
+
sort_by = options[:sort] || page_params[:sort] || ActiveRecordProfiler::Collector::DURATION
|
8
|
+
|
9
|
+
content_tag(:form) do
|
10
|
+
[
|
11
|
+
hidden_field_tag(:sort, sort_by),
|
12
|
+
label_tag(:profiler_date, 'Filter by date-hour prefix (yyyy-mm-dd-hh):'),
|
13
|
+
text_field_tag(:date, date, {:size =>10, :id => :profiler_date}),
|
14
|
+
submit_tag('Go')
|
15
|
+
].join.html_safe
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def profiler_report(page_params, options = {})
|
20
|
+
date = options[:date] || page_params[:date] || Time.now.strftime(ActiveRecordProfiler::Collector::DATE_FORMAT)
|
21
|
+
sort_by = (options[:sort] || page_params[:sort] || ActiveRecordProfiler::Collector::DURATION).to_i
|
22
|
+
max_rows = (options[:max_rows] || page_params[:max_rows] || 100).to_i
|
23
|
+
collector = options[:collector] || ActiveRecordProfiler::Collector.new
|
24
|
+
|
25
|
+
options = options.reverse_merge(:table => 'profiler', :link_locations => false)
|
26
|
+
|
27
|
+
totals = collector.aggregate(:prefix => date)
|
28
|
+
top_locs = collector.sorted_locations(sort_by.to_i, max_rows)
|
29
|
+
|
30
|
+
rows = []
|
31
|
+
|
32
|
+
if top_locs.present?
|
33
|
+
top_item = totals[top_locs.first]
|
34
|
+
max_bar = sort_by == ActiveRecordProfiler::Collector::AVG_DURATION ? (top_item[0]/top_item[1]) : top_item[sort_by]
|
35
|
+
|
36
|
+
top_locs.each do |location|
|
37
|
+
rows << content_tag(
|
38
|
+
:tr,
|
39
|
+
profiler_report_cols(location, totals[location], sort_by, max_bar, options),
|
40
|
+
{:class => "#{cycle('oddRow', 'evenRow')} #{options[:row]}"}
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
content_tag(:table, {:class => options[:table]}) do
|
46
|
+
[
|
47
|
+
content_tag(:thead,
|
48
|
+
profiler_report_header(page_params, sort_by, max_rows, options)
|
49
|
+
),
|
50
|
+
content_tag(:tbody) do
|
51
|
+
rows.join.html_safe
|
52
|
+
end
|
53
|
+
].join.html_safe
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def profiler_column_header_link(column_id, sort_by, page_params)
|
58
|
+
labels = {
|
59
|
+
ActiveRecordProfiler::Collector::DURATION => '<b>Total Duration (s)</b>',
|
60
|
+
ActiveRecordProfiler::Collector::COUNT => '<b>Count</b>',
|
61
|
+
ActiveRecordProfiler::Collector::AVG_DURATION => '<b>Avg. Duration (ms)</b>',
|
62
|
+
ActiveRecordProfiler::Collector::LONGEST => '<b>Max. Duration (ms)</b>',
|
63
|
+
}
|
64
|
+
|
65
|
+
link_to_unless(sort_by == column_id,
|
66
|
+
labels[column_id].html_safe,
|
67
|
+
page_params.merge(:sort => column_id)
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def profiler_report_header(page_params, sort_by, max_rows, options)
|
72
|
+
column_styles = options[:column_styles] || {
|
73
|
+
:location => 'width:19%',
|
74
|
+
:total_duration => 'width:6%',
|
75
|
+
:count => 'width:6%',
|
76
|
+
:average_duration => 'width:6%',
|
77
|
+
:max_duration => 'width:6%',
|
78
|
+
:longest_sql => 'width:57%'
|
79
|
+
}
|
80
|
+
|
81
|
+
headers = []
|
82
|
+
|
83
|
+
headers << content_tag(:th, 'Location', {:style => column_styles[:location]})
|
84
|
+
|
85
|
+
headers << content_tag(
|
86
|
+
:th,
|
87
|
+
profiler_column_header_link(ActiveRecordProfiler::Collector::DURATION, sort_by, page_params),
|
88
|
+
{:style => column_styles[:total_duration]}
|
89
|
+
)
|
90
|
+
|
91
|
+
headers << content_tag(
|
92
|
+
:th,
|
93
|
+
profiler_column_header_link(ActiveRecordProfiler::Collector::COUNT, sort_by, page_params),
|
94
|
+
{:style => column_styles[:count]}
|
95
|
+
)
|
96
|
+
|
97
|
+
headers << content_tag(
|
98
|
+
:th,
|
99
|
+
profiler_column_header_link(ActiveRecordProfiler::Collector::AVG_DURATION, sort_by, page_params),
|
100
|
+
{:style => column_styles[:average_duration]}
|
101
|
+
)
|
102
|
+
|
103
|
+
headers << content_tag(
|
104
|
+
:th,
|
105
|
+
profiler_column_header_link(ActiveRecordProfiler::Collector::LONGEST, sort_by, page_params),
|
106
|
+
{:style => column_styles[:max_duration]}
|
107
|
+
)
|
108
|
+
|
109
|
+
headers << content_tag(:th, 'SQL for Max Duration', {:style => column_styles[:longest_sql]})
|
110
|
+
|
111
|
+
content_tag(:tr, {:class => options[:header]}) do
|
112
|
+
headers.join.html_safe
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def profiler_report_cols(location, row_data, sort_by, max_bar, options)
|
117
|
+
columns = []
|
118
|
+
|
119
|
+
loc_parts = location.split(':')
|
120
|
+
breakble_loc = loc_parts[0].gsub('/', "​/")
|
121
|
+
this_bar = sort_by == ActiveRecordProfiler::Collector::AVG_DURATION ? (row_data[0]/row_data[1]) : row_data[sort_by]
|
122
|
+
|
123
|
+
columns << content_tag(:td, {:title=>loc_parts[2]}) do
|
124
|
+
[
|
125
|
+
link_to_if(
|
126
|
+
options[:link_locations],
|
127
|
+
"#{breakble_loc}: #{loc_parts[1]}".html_safe,
|
128
|
+
"javascript:showSourceFile('#{loc_parts[0]}', '#{loc_parts[1]}');"
|
129
|
+
),
|
130
|
+
content_tag(:div, '', {:style=>"background-color:red; height:1.2em; width:#{100*this_bar/max_bar}%"})
|
131
|
+
].join.html_safe
|
132
|
+
end
|
133
|
+
|
134
|
+
columns << content_tag(:td, number_with_precision(row_data[0], :precision => 3), {:class => "numeric"})
|
135
|
+
columns << content_tag(:td, number_with_delimiter(row_data[1]), {:class => "numeric"})
|
136
|
+
columns << content_tag(:td, number_with_precision(row_data[0]/row_data[1] * 1000, :precision => 3), {:class => "numeric"})
|
137
|
+
columns << content_tag(:td, number_with_precision(row_data[2] * 1000, :precision => 3), {:class => "numeric"})
|
138
|
+
columns << content_tag(:td, h(row_data[3]), {:class => "sql"})
|
139
|
+
|
140
|
+
columns.join('').html_safe
|
141
|
+
end
|
142
|
+
|
143
|
+
def profiler_report_local_path_form
|
144
|
+
content_tag(:form,
|
145
|
+
[
|
146
|
+
label_tag(:source_root, 'Local gistweb source path (for location links):'),
|
147
|
+
text_field_tag(:source_root, nil, {:size => 50, :id => :source_root})
|
148
|
+
]
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
def profiler_local_path_link_formatters
|
153
|
+
@@profiler_local_path_link_formatters ||= {
|
154
|
+
:textmate => '"txmt://open/?url=file://" + root + "/" + file + "&line=" + line',
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
def profile_report_local_path_javascript(link_format = :textmate)
|
159
|
+
formatter = profiler_local_path_link_formatters[link_format]
|
160
|
+
|
161
|
+
javascript_tag(%Q[
|
162
|
+
var profiler_source_root = $('#source_root');
|
163
|
+
|
164
|
+
function setDBProfSourceRoot(value) {
|
165
|
+
var exdate = new Date();
|
166
|
+
var expireDays = 356;
|
167
|
+
exdate.setDate(exdate.getDate() + expireDays);
|
168
|
+
document.cookie = "db_prof_source_root=" + escape(value) + ";expires=" + exdate.toGMTString();
|
169
|
+
}
|
170
|
+
|
171
|
+
function getDBProfSourceRoot() {
|
172
|
+
if (document.cookie.length>0) {
|
173
|
+
var c_name = "db_prof_source_root";
|
174
|
+
c_start = document.cookie.indexOf(c_name + "=");
|
175
|
+
if (c_start != -1) {
|
176
|
+
c_start = c_start + c_name.length + 1;
|
177
|
+
c_end = document.cookie.indexOf(";", c_start);
|
178
|
+
if (c_end == -1) { c_end = document.cookie.length; }
|
179
|
+
var root = document.cookie.substring(c_start, c_end);
|
180
|
+
if (root != "") {
|
181
|
+
return unescape(root);
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
return "#{Rails.root}";
|
186
|
+
}
|
187
|
+
function showSourceFile(file, line){
|
188
|
+
var root = profiler_source_root.val();
|
189
|
+
if (root == "") { root = "#{Rails.root}"}
|
190
|
+
window.location = #{formatter};
|
191
|
+
}
|
192
|
+
|
193
|
+
profiler_source_root.val(getDBProfSourceRoot());
|
194
|
+
profiler_source_root.change(function(e){
|
195
|
+
setDBProfSourceRoot($(this).val());
|
196
|
+
});
|
197
|
+
])
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active-record-profiler'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module ActiveRecordProfiler
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer "active_record_profiler.add_to_abstract_adapter" do |app|
|
7
|
+
# ActiveRecordProfiler::LogSubscriber.attach_to :active_record
|
8
|
+
ActionView::Base.send :include, ProfilerViewHelper
|
9
|
+
|
10
|
+
Collector.trim_root_path = "#{Rails.root.expand_path}/"
|
11
|
+
Collector.profile_dir = Rails.root.join("log", "profiler_data")
|
12
|
+
end
|
13
|
+
|
14
|
+
rake_tasks do
|
15
|
+
load 'active-record-profiler/tasks.rake'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
namespace "profiler" do
|
2
|
+
|
3
|
+
desc 'Aggregate profile data and display locations with the longest total durations'
|
4
|
+
task :aggregate => :environment do
|
5
|
+
top_n = ENV['max_lines'].present? ? ENV['max_lines'].to_i : 50
|
6
|
+
show_longest_sql = ENV['show_sql'] == 'true' ? true : false
|
7
|
+
prefix = ENV['prefix'].present? ? ENV['prefix'] : nil
|
8
|
+
compact = case ENV['compact']
|
9
|
+
when 'date'
|
10
|
+
:date
|
11
|
+
when 'hour'
|
12
|
+
:hour
|
13
|
+
else
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
if compact && prefix.nil?
|
18
|
+
case compact
|
19
|
+
when :date
|
20
|
+
prefix = 1.day.ago.strftime(ActiveRecordProfiler::Collector::DATE_FORMAT)
|
21
|
+
when :hour
|
22
|
+
prefix = 1.hour.ago.strftime(ActiveRecordProfiler::Collector::DATE_FORMAT + ActiveRecordProfiler::HOUR_FORMAT)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
collector = ActiveRecordProfiler::Collector.new
|
27
|
+
totals = collector.aggregate(:prefix => prefix, :compact => compact)
|
28
|
+
top_locs = collector.sorted_locations(ActiveRecordProfiler::Collector::DURATION, top_n)
|
29
|
+
|
30
|
+
top_locs.each do |loc|
|
31
|
+
data = show_longest_sql ? totals[loc] : totals[loc][0..-2]
|
32
|
+
puts "#{loc}: #{data.join(', ')}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'Clear out the profiler data diretory'
|
37
|
+
task :clear_data => :environment do
|
38
|
+
ActiveRecordProfiler::Collector.clear_data
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test_helper.rb'
|
2
|
+
|
3
|
+
class ActiveRecordProfilerTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
@collector = ActiveRecordProfiler::Collector.instance
|
6
|
+
@test_log = StringIO.new
|
7
|
+
ActiveRecord::Base.logger = ActiveRecordProfiler::Logger.new(
|
8
|
+
ActiveSupport::Logger.new(@test_log))
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_caller_location_appears_in_log
|
12
|
+
sql = 'SELECT 1 FROM widgets'
|
13
|
+
ActiveRecord::Base.connection.select_value(sql)
|
14
|
+
@test_log.rewind
|
15
|
+
log_data = @test_log.read
|
16
|
+
assert_match Regexp.new(Regexp.quote(sql) + '.*' + Regexp.quote('active_record_profiler_test.rb')), log_data
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_profiler_records_query_site
|
20
|
+
assert @collector
|
21
|
+
@collector.flush_query_sites_statistics
|
22
|
+
assert @collector.query_sites.blank?
|
23
|
+
sql = 'SELECT 1 FROM widgets'
|
24
|
+
ActiveRecord::Base.connection.select_value(sql)
|
25
|
+
@test_log.rewind
|
26
|
+
assert @collector.query_sites.present?
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/test/database.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
== README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
28
|
+
<tt>rake doc:app</tt>.
|
data/test/dummy/Rakefile
ADDED
File without changes
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|