rake_audit 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.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +38 -0
  3. data/LICENSE +7 -0
  4. data/README.md +235 -0
  5. data/app/controllers/rake_audit/application_controller.rb +53 -0
  6. data/app/controllers/rake_audit/dashboard_controller.rb +36 -0
  7. data/app/controllers/rake_audit/executions_controller.rb +39 -0
  8. data/app/models/rake_audit/task_execution.rb +33 -0
  9. data/app/views/layouts/rake_audit/application.html.erb +70 -0
  10. data/app/views/rake_audit/dashboard/index.html.erb +48 -0
  11. data/app/views/rake_audit/executions/index.html.erb +74 -0
  12. data/app/views/rake_audit/executions/show.html.erb +46 -0
  13. data/config/routes.rb +13 -0
  14. data/db/migrate/20260531120000_create_rake_task_executions.rb +62 -0
  15. data/lib/generators/rake_audit/install_generator.rb +42 -0
  16. data/lib/generators/rake_audit/templates/create_rake_task_executions.rb +55 -0
  17. data/lib/generators/rake_audit/templates/initializer.rb +17 -0
  18. data/lib/rake_audit/adapters/active_record_adapter.rb +73 -0
  19. data/lib/rake_audit/adapters/base.rb +94 -0
  20. data/lib/rake_audit/adapters/execution_record.rb +20 -0
  21. data/lib/rake_audit/adapters/mongo_adapter.rb +137 -0
  22. data/lib/rake_audit/adapters/redis_adapter.rb +174 -0
  23. data/lib/rake_audit/builders/task_execution_record_builder.rb +81 -0
  24. data/lib/rake_audit/configuration.rb +62 -0
  25. data/lib/rake_audit/execution_recorder.rb +78 -0
  26. data/lib/rake_audit/rails/engine.rb +20 -0
  27. data/lib/rake_audit/rails/railtie.rb +17 -0
  28. data/lib/rake_audit/record_not_found.rb +5 -0
  29. data/lib/rake_audit/task_execution_record.rb +46 -0
  30. data/lib/rake_audit/task_patch.rb +19 -0
  31. data/lib/rake_audit/version.rb +5 -0
  32. data/lib/rake_audit.rb +82 -0
  33. metadata +120 -0
@@ -0,0 +1,46 @@
1
+ <h1>Execution #<%= @execution.id %></h1>
2
+
3
+ <p><%= link_to "← Back to executions", executions_path %></p>
4
+
5
+ <section class="section">
6
+ <h2>Task Info</h2>
7
+ <dl>
8
+ <dt>Task Name</dt><dd><%= @execution.task_name %></dd>
9
+ <dt>Status</dt>
10
+ <dd class="status-<%= @execution.status %>"><%= @execution.status %></dd>
11
+ </dl>
12
+ </section>
13
+
14
+ <section class="section">
15
+ <h2>Arguments</h2>
16
+ <pre><%= JSON.pretty_generate(@execution.arguments) if @execution.arguments.present? %></pre>
17
+ </section>
18
+
19
+ <section class="section">
20
+ <h2>Execution Info</h2>
21
+ <dl>
22
+ <dt>Started At</dt><dd><%= @execution.started_at %></dd>
23
+ <dt>Finished At</dt><dd><%= @execution.finished_at %></dd>
24
+ <dt>Duration (ms)</dt><dd><%= @execution.duration_ms %></dd>
25
+ </dl>
26
+ </section>
27
+
28
+ <% unless @execution.status == "success" %>
29
+ <section class="section">
30
+ <h2>Error Info</h2>
31
+ <dl>
32
+ <dt>Error Class</dt><dd><%= @execution.error_class %></dd>
33
+ <dt>Error Message</dt><dd><%= @execution.error_message %></dd>
34
+ </dl>
35
+ </section>
36
+ <% end %>
37
+
38
+ <section class="section">
39
+ <h2>Environment Info</h2>
40
+ <dl>
41
+ <dt>Hostname</dt><dd><%= @execution.hostname %></dd>
42
+ <dt>PID</dt><dd><%= @execution.pid %></dd>
43
+ <dt>Ruby Version</dt><dd><%= @execution.ruby_version %></dd>
44
+ <dt>Rails Env</dt><dd><%= @execution.rails_env %></dd>
45
+ </dl>
46
+ </section>
data/config/routes.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Routes for the mounted RakeAudit Engine.
4
+ #
5
+ # Because the Engine isolates its namespace, these paths live beneath the mount
6
+ # point the host application chooses (conventionally +/rake_audit+). The root
7
+ # points at the execution list so the mount point itself is useful; the
8
+ # dashboard and the execution +index+/+show+ actions round out the UI.
9
+ RakeAudit::Engine.routes.draw do
10
+ root to: 'executions#index'
11
+ get 'dashboard', to: 'dashboard#index'
12
+ resources :executions, only: %i[index show]
13
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Creates the +rake_task_executions+ table that backs
4
+ # {RakeAudit::TaskExecution}.
5
+ #
6
+ # The +arguments+ column type is chosen per database so the same migration runs
7
+ # on PostgreSQL, MySQL, and SQLite: +jsonb+ on PostgreSQL, +json+ on MySQL, and
8
+ # +text+ everywhere else (SQLite stores the serialized JSON as text). All six
9
+ # indexes from the specification are created: four single-column lookups plus
10
+ # two composite indexes that match the Web UI's primary query patterns.
11
+ class CreateRakeTaskExecutions < ActiveRecord::Migration[6.1]
12
+ def change
13
+ create_table :rake_task_executions do |t|
14
+ t.string :task_name, null: false, limit: 255
15
+ t.column :arguments, arguments_column_type, null: true
16
+ t.datetime :started_at, null: false
17
+ t.datetime :finished_at, null: false
18
+ t.bigint :duration_ms, null: true
19
+ t.string :status, null: false, limit: 20
20
+ t.string :error_class, null: true, limit: 255
21
+ t.text :error_message, null: true
22
+ t.string :hostname, null: true, limit: 255
23
+ t.integer :pid, null: true
24
+ t.string :ruby_version, null: true, limit: 50
25
+ t.string :rails_env, null: true, limit: 50
26
+
27
+ t.timestamps
28
+ end
29
+
30
+ add_audit_indexes
31
+ end
32
+
33
+ private
34
+
35
+ # Add the four single-column and two composite indexes that back the audit
36
+ # table's lookup and Web UI query patterns.
37
+ #
38
+ # @return [void]
39
+ def add_audit_indexes
40
+ add_index :rake_task_executions, :task_name
41
+ add_index :rake_task_executions, :status
42
+ add_index :rake_task_executions, :started_at
43
+ add_index :rake_task_executions, :hostname
44
+ add_index :rake_task_executions, %i[task_name started_at]
45
+ add_index :rake_task_executions, %i[status started_at]
46
+ end
47
+
48
+ # Pick a JSON-capable column type the current database supports, falling back
49
+ # to +text+ (used by SQLite) so the migration is portable across backends.
50
+ #
51
+ # @return [Symbol]
52
+ def arguments_column_type
53
+ adapter = connection.adapter_name.downcase
54
+ if adapter.include?('postgresql')
55
+ :jsonb
56
+ elsif adapter.include?('mysql')
57
+ :json
58
+ else
59
+ :text
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
5
+
6
+ module RakeAudit
7
+ module Generators
8
+ # Scaffolds RakeAudit into a host Rails application.
9
+ #
10
+ # rails generate rake_audit:install
11
+ #
12
+ # Running the generator copies a commented-out initializer to
13
+ # +config/initializers/rake_audit.rb+ and writes a timestamped migration to
14
+ # +db/migrate+ that creates the +rake_task_executions+ table. The migration
15
+ # is produced through +migration_template+ so the host's schema version and
16
+ # migration numbering are respected.
17
+ class InstallGenerator < ::Rails::Generators::Base
18
+ include ::ActiveRecord::Generators::Migration
19
+
20
+ source_root File.expand_path('templates', __dir__)
21
+
22
+ desc 'Creates the RakeAudit initializer and the rake_task_executions migration.'
23
+
24
+ # Copy the pre-filled, fully commented initializer into the host app.
25
+ #
26
+ # @return [void]
27
+ def create_initializer
28
+ template 'initializer.rb', 'config/initializers/rake_audit.rb'
29
+ end
30
+
31
+ # Generate the timestamped migration that creates the audit table.
32
+ #
33
+ # @return [void]
34
+ def create_migration_file
35
+ migration_template(
36
+ 'create_rake_task_executions.rb',
37
+ 'db/migrate/create_rake_task_executions.rb'
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Creates the +rake_task_executions+ table that backs
4
+ # RakeAudit::TaskExecution.
5
+ #
6
+ # The +arguments+ column type is chosen per database so the same migration runs
7
+ # on PostgreSQL, MySQL, and SQLite: +jsonb+ on PostgreSQL, +json+ on MySQL, and
8
+ # +text+ everywhere else (SQLite stores the serialized JSON as text).
9
+ class CreateRakeTaskExecutions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
10
+ def change
11
+ create_table :rake_task_executions do |t|
12
+ t.string :task_name, null: false, limit: 255
13
+ t.column :arguments, arguments_column_type, null: true
14
+ t.datetime :started_at, null: false
15
+ t.datetime :finished_at, null: false
16
+ t.bigint :duration_ms, null: true
17
+ t.string :status, null: false, limit: 20
18
+ t.string :error_class, null: true, limit: 255
19
+ t.text :error_message, null: true
20
+ t.string :hostname, null: true, limit: 255
21
+ t.integer :pid, null: true
22
+ t.string :ruby_version, null: true, limit: 50
23
+ t.string :rails_env, null: true, limit: 50
24
+
25
+ t.timestamps
26
+ end
27
+
28
+ add_audit_indexes
29
+ end
30
+
31
+ private
32
+
33
+ # Add the four single-column and two composite indexes.
34
+ def add_audit_indexes
35
+ add_index :rake_task_executions, :task_name
36
+ add_index :rake_task_executions, :status
37
+ add_index :rake_task_executions, :started_at
38
+ add_index :rake_task_executions, :hostname
39
+ add_index :rake_task_executions, %i[task_name started_at]
40
+ add_index :rake_task_executions, %i[status started_at]
41
+ end
42
+
43
+ # Pick a JSON-capable column type the current database supports, falling back
44
+ # to +text+ (used by SQLite) so the migration is portable across backends.
45
+ def arguments_column_type
46
+ adapter = connection.adapter_name.downcase
47
+ if adapter.include?('postgresql')
48
+ :jsonb
49
+ elsif adapter.include?('mysql')
50
+ :json
51
+ else
52
+ :text
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RakeAudit configuration.
4
+ #
5
+ # Every option below is shown with its default and left commented out. Uncomment
6
+ # and edit the lines you want to override. At minimum, set an adapter to enable
7
+ # recording — without one, RakeAudit loads but records nothing.
8
+ RakeAudit.configure do |config|
9
+ # config.adapter = RakeAudit::Adapters::ActiveRecordAdapter.new
10
+ # config.logger = Rails.logger
11
+ # config.capture_hostname = true
12
+ # config.capture_pid = true
13
+ # config.capture_ruby_version = true
14
+ # config.capture_rails_env = true
15
+ # config.web_ui_enabled = true
16
+ # config.authenticate_with = ->(controller) { controller.authenticate_admin! }
17
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../record_not_found'
5
+
6
+ module RakeAudit
7
+ module Adapters
8
+ # Persists and queries TaskExecutionRecord instances via ActiveRecord.
9
+ class ActiveRecordAdapter < Base
10
+ EXACT_FILTERS = %i[task_name status hostname rails_env].freeze
11
+ private_constant :EXACT_FILTERS
12
+
13
+ # Create a TaskExecution row from the given record.
14
+ #
15
+ # @param record [TaskExecutionRecord]
16
+ def save(record)
17
+ RakeAudit::TaskExecution.create!(record.to_h)
18
+ end
19
+
20
+ # @param filters [Hash] pre-validated non-blank filter values.
21
+ # @param page [Integer, String, nil]
22
+ # @param per_page [Integer]
23
+ # @return [ActiveRecord::Relation] kaminari-paginated relation.
24
+ def query(filters: {}, page: nil, per_page: 25)
25
+ scope = RakeAudit::TaskExecution.order(started_at: :desc)
26
+
27
+ EXACT_FILTERS.each do |col|
28
+ scope = scope.where(col => filters[col]) if filters.key?(col)
29
+ end
30
+ scope = scope.where(started_at: filters[:from]..) if filters.key?(:from)
31
+ scope = scope.where(started_at: ..filters[:to]) if filters.key?(:to)
32
+
33
+ scope.page(page).per(per_page)
34
+ end
35
+
36
+ # @param id [Integer, String]
37
+ # @return [RakeAudit::TaskExecution]
38
+ # @raise [RakeAudit::RecordNotFound]
39
+ def find(id)
40
+ RakeAudit::TaskExecution.find(id)
41
+ rescue ActiveRecord::RecordNotFound
42
+ raise RakeAudit::RecordNotFound, "Couldn't find execution with id=#{id}"
43
+ end
44
+
45
+ # @return [Integer]
46
+ def count
47
+ RakeAudit::TaskExecution.count
48
+ end
49
+
50
+ # @param status [String]
51
+ # @return [Integer]
52
+ def count_by_status(status)
53
+ RakeAudit::TaskExecution.where(status: status).count
54
+ end
55
+
56
+ # @return [Float, nil]
57
+ def average_duration_ms
58
+ RakeAudit::TaskExecution.average(:duration_ms)
59
+ end
60
+
61
+ # @param limit [Integer]
62
+ # @return [Array<Array(String, Integer)>]
63
+ def top_failed_tasks(limit: 10)
64
+ RakeAudit::TaskExecution
65
+ .where(status: 'failure')
66
+ .group(:task_name)
67
+ .count
68
+ .sort_by { |_, c| -c }
69
+ .first(limit)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RakeAudit
4
+ module Adapters
5
+ # Abstract storage adapter defining the full persistence and query contract.
6
+ #
7
+ # Every concrete adapter must implement +#save+. Adapters that back the Web
8
+ # UI must also implement the six query methods below; the default
9
+ # implementations raise +NotImplementedError+ so omissions surface loudly.
10
+ class Base
11
+ # Persist a TaskExecutionRecord.
12
+ #
13
+ # @param record [TaskExecutionRecord] the record to save.
14
+ # @raise [NotImplementedError] when not overridden.
15
+ def save(record)
16
+ raise NotImplementedError, "#{self.class}#save is not implemented"
17
+ end
18
+
19
+ # Return a kaminari-paginated, filtered, newest-first collection.
20
+ #
21
+ # @param filters [Hash] any subset of: +:task_name+, +:status+,
22
+ # +:hostname+, +:rails_env+, +:from+, +:to+ (all pre-validated,
23
+ # non-blank values only — blank keys are never present).
24
+ # @param page [Integer, String, nil] 1-based page number.
25
+ # @param per_page [Integer] records per page.
26
+ # @raise [NotImplementedError] when not overridden.
27
+ def query(filters: {}, page: nil, per_page: 25)
28
+ raise NotImplementedError, "#{self.class}#query is not implemented"
29
+ end
30
+
31
+ # Return a single execution record by ID.
32
+ #
33
+ # @param id [String, Integer] adapter-specific identifier.
34
+ # @raise [RakeAudit::RecordNotFound] when no record matches.
35
+ # @raise [NotImplementedError] when not overridden.
36
+ def find(id)
37
+ raise NotImplementedError, "#{self.class}#find is not implemented"
38
+ end
39
+
40
+ # Total number of stored executions.
41
+ #
42
+ # @return [Integer]
43
+ # @raise [NotImplementedError] when not overridden.
44
+ def count
45
+ raise NotImplementedError, "#{self.class}#count is not implemented"
46
+ end
47
+
48
+ # Number of executions whose +status+ equals +status+.
49
+ #
50
+ # @param status [String] e.g. <tt>"success"</tt> or <tt>"failure"</tt>.
51
+ # @return [Integer]
52
+ # @raise [NotImplementedError] when not overridden.
53
+ def count_by_status(status)
54
+ raise NotImplementedError, "#{self.class}#count_by_status is not implemented"
55
+ end
56
+
57
+ # Mean +duration_ms+ across all stored executions, or +nil+ when empty.
58
+ #
59
+ # @return [Float, nil]
60
+ # @raise [NotImplementedError] when not overridden.
61
+ def average_duration_ms
62
+ raise NotImplementedError, "#{self.class}#average_duration_ms is not implemented"
63
+ end
64
+
65
+ # The +limit+ task names with the most failures, ordered descending.
66
+ #
67
+ # @param limit [Integer]
68
+ # @return [Array<Array(String, Integer)>] e.g. +[["db:migrate", 5], ...]+
69
+ # @raise [NotImplementedError] when not overridden.
70
+ def top_failed_tasks(limit: 10)
71
+ raise NotImplementedError, "#{self.class}#top_failed_tasks is not implemented"
72
+ end
73
+
74
+ # All five dashboard metrics in a single call.
75
+ #
76
+ # The default implementation delegates to the five individual methods, which
77
+ # is efficient for SQL-backed adapters (five fast queries). Adapters whose
78
+ # individual methods are expensive (e.g. Redis, which reads the full list
79
+ # per call) should override this to compute everything in one pass.
80
+ #
81
+ # @return [Hash] with keys +:total+, +:success_count+, +:failure_count+,
82
+ # +:average_duration_ms+, +:top_failed_tasks+.
83
+ def stats
84
+ {
85
+ total: count,
86
+ success_count: count_by_status('success'),
87
+ failure_count: count_by_status('failure'),
88
+ average_duration_ms: average_duration_ms,
89
+ top_failed_tasks: top_failed_tasks
90
+ }
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RakeAudit
4
+ module Adapters
5
+ # Read-side value object returned by non-ActiveRecord adapters.
6
+ #
7
+ # Carries every field the Web UI views access plus an +id+ and +to_param+
8
+ # so Rails route helpers (+execution_path(record)+) work without AR.
9
+ ExecutionRecord = Struct.new(
10
+ :id, :task_name, :arguments, :started_at, :finished_at,
11
+ :duration_ms, :status, :error_class, :error_message,
12
+ :hostname, :pid, :ruby_version, :rails_env,
13
+ keyword_init: true
14
+ ) do
15
+ def to_param
16
+ id.to_s
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative 'execution_record'
5
+ require_relative '../record_not_found'
6
+
7
+ module RakeAudit
8
+ module Adapters
9
+ # Persists and queries TaskExecutionRecord instances via MongoDB.
10
+ #
11
+ # Uses the MongoDB Ruby driver directly. Documents are stored in the
12
+ # +rake_task_executions+ collection and Mongo's native +_id+ (ObjectId) is
13
+ # used as the record identifier for Web UI links.
14
+ class MongoAdapter < Base
15
+ # @param client [Mongo::Client] a connected MongoDB client instance.
16
+ def initialize(client:)
17
+ super()
18
+ @client = client
19
+ end
20
+
21
+ # Insert the record into the executions collection.
22
+ #
23
+ # @param record [TaskExecutionRecord]
24
+ def save(record)
25
+ collection.insert_one(record.to_h)
26
+ end
27
+
28
+ # @param filters [Hash] pre-validated non-blank filter values.
29
+ # @param page [Integer, String, nil]
30
+ # @param per_page [Integer]
31
+ # @return [Kaminari::PaginatableArray]
32
+ def query(filters: {}, page: nil, per_page: 25)
33
+ require 'kaminari'
34
+ filter = build_filter(filters)
35
+ total = collection.count_documents(filter)
36
+ current = [page.to_i, 1].max
37
+ cursor = collection.find(filter)
38
+ .sort(started_at: -1)
39
+ .skip((current - 1) * per_page)
40
+ .limit(per_page)
41
+
42
+ records = cursor.map { |doc| to_execution_record(doc) }
43
+ Kaminari
44
+ .paginate_array(records, total_count: total)
45
+ .page(current)
46
+ .per(per_page)
47
+ end
48
+
49
+ # @param id [String] string representation of a Mongo ObjectId.
50
+ # @return [RakeAudit::Adapters::ExecutionRecord]
51
+ # @raise [RakeAudit::RecordNotFound]
52
+ def find(id)
53
+ # Skip the require when BSON is already defined (e.g. stubbed in tests).
54
+ require 'bson' unless defined?(BSON)
55
+ object_id = BSON::ObjectId.from_string(id.to_s)
56
+ doc = collection.find(_id: object_id).first
57
+ raise RakeAudit::RecordNotFound, "Couldn't find execution with id=#{id}" unless doc
58
+
59
+ to_execution_record(doc)
60
+ rescue ArgumentError
61
+ # BSON::ObjectId::Invalid inherits from ArgumentError — invalid id string.
62
+ raise RakeAudit::RecordNotFound, "Couldn't find execution with id=#{id}"
63
+ end
64
+
65
+ # @return [Integer]
66
+ def count
67
+ collection.count_documents({})
68
+ end
69
+
70
+ # @param status [String]
71
+ # @return [Integer]
72
+ def count_by_status(status)
73
+ collection.count_documents('status' => status)
74
+ end
75
+
76
+ # @return [Float, nil]
77
+ def average_duration_ms
78
+ result = collection.aggregate([
79
+ { '$group' => { '_id' => nil, 'avg' => { '$avg' => '$duration_ms' } } }
80
+ ]).first
81
+ result ? result['avg'] : nil
82
+ end
83
+
84
+ # @param limit [Integer]
85
+ # @return [Array<Array(String, Integer)>]
86
+ def top_failed_tasks(limit: 10)
87
+ collection.aggregate([
88
+ { '$match' => { 'status' => 'failure' } },
89
+ { '$group' => { '_id' => '$task_name', 'count' => { '$sum' => 1 } } },
90
+ { '$sort' => { 'count' => -1 } },
91
+ { '$limit' => limit }
92
+ ]).map { |doc| [doc['_id'], doc['count']] }
93
+ end
94
+
95
+ private
96
+
97
+ def collection
98
+ @client['rake_task_executions']
99
+ end
100
+
101
+ def build_filter(filters)
102
+ filter = {}
103
+
104
+ %i[task_name status hostname rails_env].each do |col|
105
+ filter[col.to_s] = filters[col] if filters.key?(col)
106
+ end
107
+
108
+ if filters.key?(:from) || filters.key?(:to)
109
+ range = {}
110
+ range['$gte'] = filters[:from].to_s if filters.key?(:from)
111
+ range['$lte'] = filters[:to].to_s if filters.key?(:to)
112
+ filter['started_at'] = range
113
+ end
114
+
115
+ filter
116
+ end
117
+
118
+ def to_execution_record(doc)
119
+ RakeAudit::Adapters::ExecutionRecord.new(
120
+ id: doc['_id']&.to_s,
121
+ task_name: doc['task_name'],
122
+ arguments: doc['arguments'],
123
+ started_at: doc['started_at'],
124
+ finished_at: doc['finished_at'],
125
+ duration_ms: doc['duration_ms'],
126
+ status: doc['status'],
127
+ error_class: doc['error_class'],
128
+ error_message: doc['error_message'],
129
+ hostname: doc['hostname'],
130
+ pid: doc['pid'],
131
+ ruby_version: doc['ruby_version'],
132
+ rails_env: doc['rails_env']
133
+ )
134
+ end
135
+ end
136
+ end
137
+ end