rails_ops 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +84 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +1216 -0
- data/RUBY_VERSION +1 -0
- data/Rakefile +39 -0
- data/VERSION +1 -0
- data/lib/rails_ops.rb +96 -0
- data/lib/rails_ops/authorization_backend/abstract.rb +7 -0
- data/lib/rails_ops/authorization_backend/can_can_can.rb +14 -0
- data/lib/rails_ops/configuration.rb +4 -0
- data/lib/rails_ops/context.rb +35 -0
- data/lib/rails_ops/controller_mixin.rb +105 -0
- data/lib/rails_ops/exceptions.rb +19 -0
- data/lib/rails_ops/hooked_job.rb +25 -0
- data/lib/rails_ops/hookup.rb +80 -0
- data/lib/rails_ops/hookup/dsl.rb +29 -0
- data/lib/rails_ops/hookup/dsl_validator.rb +45 -0
- data/lib/rails_ops/hookup/hook.rb +11 -0
- data/lib/rails_ops/log_subscriber.rb +24 -0
- data/lib/rails_ops/mixins.rb +2 -0
- data/lib/rails_ops/mixins/authorization.rb +83 -0
- data/lib/rails_ops/mixins/log_settings.rb +20 -0
- data/lib/rails_ops/mixins/model.rb +4 -0
- data/lib/rails_ops/mixins/model/authorization.rb +64 -0
- data/lib/rails_ops/mixins/model/nesting.rb +180 -0
- data/lib/rails_ops/mixins/policies.rb +42 -0
- data/lib/rails_ops/mixins/require_context.rb +33 -0
- data/lib/rails_ops/mixins/routes.rb +35 -0
- data/lib/rails_ops/mixins/schema_validation.rb +25 -0
- data/lib/rails_ops/mixins/sub_ops.rb +35 -0
- data/lib/rails_ops/model_casting.rb +17 -0
- data/lib/rails_ops/model_mixins.rb +12 -0
- data/lib/rails_ops/model_mixins/ar_extension.rb +20 -0
- data/lib/rails_ops/model_mixins/parent_op.rb +10 -0
- data/lib/rails_ops/model_mixins/protected_attributes.rb +78 -0
- data/lib/rails_ops/model_mixins/virtual_attributes.rb +24 -0
- data/lib/rails_ops/model_mixins/virtual_attributes/virtual_column_wrapper.rb +9 -0
- data/lib/rails_ops/model_mixins/virtual_has_one.rb +32 -0
- data/lib/rails_ops/operation.rb +215 -0
- data/lib/rails_ops/operation/model.rb +168 -0
- data/lib/rails_ops/operation/model/create.rb +35 -0
- data/lib/rails_ops/operation/model/destroy.rb +26 -0
- data/lib/rails_ops/operation/model/load.rb +72 -0
- data/lib/rails_ops/operation/model/update.rb +31 -0
- data/lib/rails_ops/patches/active_type_patch.rb +52 -0
- data/lib/rails_ops/profiler.rb +47 -0
- data/lib/rails_ops/profiler/node.rb +64 -0
- data/lib/rails_ops/railtie.rb +19 -0
- data/lib/rails_ops/scoped_env.rb +20 -0
- data/lib/rails_ops/virtual_model.rb +19 -0
- data/rails_ops.gemspec +58 -0
- data/test/test_helper.rb +3 -0
- metadata +252 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
class RailsOps::Operation::Model < RailsOps::Operation
|
2
|
+
include RailsOps::Mixins::Model::Authorization
|
3
|
+
include RailsOps::Mixins::Model::Nesting
|
4
|
+
|
5
|
+
class_attribute :_model_class
|
6
|
+
|
7
|
+
# Allows to set or get a model class associated with this operation.
|
8
|
+
#
|
9
|
+
# To get the model class, call this method without any parameters. Note that
|
10
|
+
# this will result in an exception if the model class has not been previously
|
11
|
+
# set.
|
12
|
+
#
|
13
|
+
# To set the model class, there are 3 ways:
|
14
|
+
#
|
15
|
+
# - You only specify an existing class as `model_class`.
|
16
|
+
# - You only specify a block of code. This will result in a dynamically
|
17
|
+
# created class using the provided code block. The base class that is
|
18
|
+
# extended by this block is determined by the method
|
19
|
+
# {default_model_class}. Check this method to determine the used base class
|
20
|
+
# or overwrite it. This only makes sense for inheritance as there is the
|
21
|
+
# parameter `model_class` (see next point).
|
22
|
+
# - You specify a `model_class` as well as a block of code. In this case, the
|
23
|
+
# supplied class will be dynamically extended with the code block provided.
|
24
|
+
#
|
25
|
+
# This is a DSL method. Do not call it at runtime (i.e. after bootup) as it
|
26
|
+
# is not thread-safe and may generate anonymous classes.
|
27
|
+
#
|
28
|
+
# @param model_class [Class] The model class to take or extend
|
29
|
+
# @param name [String,Symbol] A custom name that will be used for generating
|
30
|
+
# the anonymous class
|
31
|
+
# @yield [] This optional block is executed in context of the newly generated,
|
32
|
+
# anonymous class and allows you to extend it
|
33
|
+
def self.model(model_class = nil, name = nil, &block)
|
34
|
+
if model_class || block_given?
|
35
|
+
fail 'Model class can only be set once.' if _model_class
|
36
|
+
|
37
|
+
model_class ||= default_model_class
|
38
|
+
|
39
|
+
# ---------------------------------------------------------------
|
40
|
+
# If we're given a block or we're configured to always extend the
|
41
|
+
# given model class, create a new model class inheriting from the
|
42
|
+
# given one and mix in required modules.
|
43
|
+
# ---------------------------------------------------------------
|
44
|
+
if block_given? || always_extend_model_class?
|
45
|
+
self._model_class = Class.new(model_class)
|
46
|
+
|
47
|
+
# Include operation mixins that provide various functionality needed for
|
48
|
+
# operation-specific models. We can safely include this even if it has
|
49
|
+
# already been included, as module includes only happen once.
|
50
|
+
_model_class.send(:include, RailsOps::ModelMixins)
|
51
|
+
|
52
|
+
# Apply the given block to the newly created class.
|
53
|
+
_model_class.class_eval(&block) if block_given?
|
54
|
+
|
55
|
+
# Set virtual model name if given.
|
56
|
+
if name && _model_class.respond_to?(:virtual_model_name=)
|
57
|
+
_model_class.virtual_model_name = ActiveModel::Name.new(_model_class, nil, name.to_s)
|
58
|
+
end
|
59
|
+
|
60
|
+
# ---------------------------------------------------------------
|
61
|
+
# We just use the given model class without any adaptions
|
62
|
+
# ---------------------------------------------------------------
|
63
|
+
else
|
64
|
+
self._model_class = model_class
|
65
|
+
end
|
66
|
+
elsif _model_class.nil?
|
67
|
+
fail 'No model class has been set.'
|
68
|
+
end
|
69
|
+
|
70
|
+
return _model_class
|
71
|
+
end
|
72
|
+
|
73
|
+
# This method determines whether the given model class (specified using the
|
74
|
+
# static `model` method) is always extended with an anonymous class. This can
|
75
|
+
# be required if the operation modifies the model class.
|
76
|
+
def self.always_extend_model_class?
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the class used for extension when defining a model using the {model}
|
81
|
+
# method.
|
82
|
+
def self.default_model_class
|
83
|
+
ActiveType::Object
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns an instance of the operation model class. The instance is obtained
|
87
|
+
# using {build_model} and cached for the lifespan of the operation instance.
|
88
|
+
def model
|
89
|
+
unless @model
|
90
|
+
build_model
|
91
|
+
end
|
92
|
+
|
93
|
+
return @model
|
94
|
+
end
|
95
|
+
|
96
|
+
# Assigns attributes to a model. If no arguments are given, it extracts the
|
97
|
+
# attributes from the {params} hash and assigns them to the {model}.
|
98
|
+
#
|
99
|
+
# @param attributes [Hash] The attributes hash to assign. If not given,
|
100
|
+
# attributes will be obtained from the {params} hash using
|
101
|
+
# {extract_attributes_from_params}.
|
102
|
+
# @param without_protection [Boolean] If `true`, attributes wil be assigned without
|
103
|
+
# mass assignment protection.
|
104
|
+
# @param model [ActiveRecord::Base] Allows to manually specify a model the attributes
|
105
|
+
# will be assigned to. If not given, the model will be obtained from the
|
106
|
+
# {model} method.
|
107
|
+
# @param without_nested_models [Boolean] Per default, attribute params that
|
108
|
+
# are meant for nested models (registered via `nested_model_op`) will not
|
109
|
+
# be assigned. You can turn this filtering off by passing `false`.
|
110
|
+
def assign_attributes(attributes = nil, model: nil, without_protection: false, without_nested_models: true)
|
111
|
+
model ||= self.model
|
112
|
+
|
113
|
+
unless attributes
|
114
|
+
# Extract attributes from params hash
|
115
|
+
attributes = extract_attributes_from_params(model)
|
116
|
+
end
|
117
|
+
|
118
|
+
if without_nested_models
|
119
|
+
# Remove parameters that will be passed to nested model operations
|
120
|
+
# such as `post_attributes: {}`.
|
121
|
+
attributes = attributes.except(*self.class.nested_model_param_keys)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Do nothing if there are no attributes to assign
|
125
|
+
return if attributes.nil? || attributes.empty?
|
126
|
+
|
127
|
+
# Assign attributes, either with or without protection
|
128
|
+
if !without_protection && model.respond_to?(:assign_attributes_with_protection)
|
129
|
+
model.assign_attributes_with_protection(attributes)
|
130
|
+
else
|
131
|
+
model.assign_attributes(attributes)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Extracts model attributes from the {params} hash using
|
136
|
+
# `model.model_name.param_key`. If no model is given, the model will be
|
137
|
+
# obtained from the {model} method.
|
138
|
+
# @param model [ActiveRecord::Base] An optional model for determining the
|
139
|
+
# param key name
|
140
|
+
def extract_attributes_from_params(model = nil)
|
141
|
+
model ||= self.model
|
142
|
+
return params[model.model_name.param_key] || {}
|
143
|
+
end
|
144
|
+
|
145
|
+
protected
|
146
|
+
|
147
|
+
# Performs nested model operations and then saves the model. If you have
|
148
|
+
# nested model operations, you should call this method instead of calling
|
149
|
+
# `model.save!` directly.
|
150
|
+
def save!
|
151
|
+
perform_nested_model_ops!
|
152
|
+
model.save!
|
153
|
+
end
|
154
|
+
|
155
|
+
# Override of the base classes method to provide the model under key `model`
|
156
|
+
# to operations called via the `after_run` hook. You can still override this
|
157
|
+
# to suit your own operation's needs.
|
158
|
+
def after_run_trigger_params
|
159
|
+
{ model: model }
|
160
|
+
end
|
161
|
+
|
162
|
+
# This method must be implemented on superclasses in order to return an
|
163
|
+
# instance of the operation model class. This can be a new or existing record,
|
164
|
+
# depending on the respective operation's needs.
|
165
|
+
def build_model
|
166
|
+
fail NotImplementedError, 'Method `build_model` must be implemented.'
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class RailsOps::Operation::Model::Create < RailsOps::Operation::Model
|
2
|
+
model_authorization_action :create
|
3
|
+
|
4
|
+
policy :on_init do
|
5
|
+
model_authorization
|
6
|
+
end
|
7
|
+
|
8
|
+
# As this operation might extend the model class, we need to make sure that
|
9
|
+
# the operation works using an extended 'copy' of the given model class.
|
10
|
+
def self.always_extend_model_class?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def model_authorization
|
15
|
+
return unless authorization_enabled?
|
16
|
+
|
17
|
+
unless model_authorization_action.nil?
|
18
|
+
authorize_model! model_authorization_action, model
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def build_model
|
23
|
+
fail 'Model can only be built once.' if @model
|
24
|
+
@model = self.class.model.new
|
25
|
+
if @model.respond_to?(:parent_op=)
|
26
|
+
@model.parent_op = self
|
27
|
+
end
|
28
|
+
build_nested_model_ops :create
|
29
|
+
assign_attributes
|
30
|
+
end
|
31
|
+
|
32
|
+
def perform
|
33
|
+
save!
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class RailsOps::Operation::Model::Destroy < RailsOps::Operation::Model::Load
|
2
|
+
model_authorization_action :destroy
|
3
|
+
|
4
|
+
def model_authorization
|
5
|
+
return unless authorization_enabled?
|
6
|
+
|
7
|
+
unless load_model_authorization_action.nil?
|
8
|
+
authorize_model_with_authorize_only! load_model_authorization_action, model
|
9
|
+
end
|
10
|
+
|
11
|
+
unless model_authorization_action.nil?
|
12
|
+
authorize_model! model_authorization_action, model
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
policy do
|
17
|
+
if model.respond_to?(:deleteable?) && !model.deleteable?
|
18
|
+
fail RailsOps::Exceptions::ModelNotDeleteable
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform
|
23
|
+
trigger :before_destroy, model: model
|
24
|
+
model.destroy!
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
|
2
|
+
class_attribute :_lock_model_at_build
|
3
|
+
class_attribute :_load_model_authorization_action
|
4
|
+
|
5
|
+
policy :on_init do
|
6
|
+
model_authorization
|
7
|
+
end
|
8
|
+
|
9
|
+
# Gets or sets the action verb used for authorizing models on load.
|
10
|
+
def self.load_model_authorization_action(*action)
|
11
|
+
if action.size == 1
|
12
|
+
self._load_model_authorization_action = action.first
|
13
|
+
elsif action.size > 1
|
14
|
+
fail ArgumentError, 'Too many arguments'
|
15
|
+
end
|
16
|
+
|
17
|
+
return _load_model_authorization_action
|
18
|
+
end
|
19
|
+
|
20
|
+
def model_authorization
|
21
|
+
return unless authorization_enabled?
|
22
|
+
|
23
|
+
unless load_model_authorization_action.nil?
|
24
|
+
authorize_model! load_model_authorization_action, model
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_model_authorization_action
|
29
|
+
self.class.load_model_authorization_action
|
30
|
+
end
|
31
|
+
|
32
|
+
load_model_authorization_action :read
|
33
|
+
|
34
|
+
def self.lock_model_at_build(enabled = true)
|
35
|
+
self._lock_model_at_build = enabled
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.lock_model_at_build?
|
39
|
+
_lock_model_at_build.nil? ? RailsOps.config.lock_models_at_build? : _lock_model_at_build
|
40
|
+
end
|
41
|
+
|
42
|
+
def model_id_field
|
43
|
+
:id
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_model
|
47
|
+
unless params[model_id_field]
|
48
|
+
fail "Param #{model_id_field.inspect} must be given."
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get model class
|
52
|
+
relation = self.class.model
|
53
|
+
|
54
|
+
# Express intention to lock if required
|
55
|
+
relation = relation.lock if self.class.lock_model_at_build?
|
56
|
+
|
57
|
+
# Fetch (and possibly lock) model
|
58
|
+
return relation.find_by!(model_id_field => params[model_id_field])
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_model
|
62
|
+
@model = find_model
|
63
|
+
|
64
|
+
if @model.respond_to?(:parent_op=)
|
65
|
+
@model.parent_op = self
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def extract_id_from_params
|
70
|
+
params[model_id_field]
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class RailsOps::Operation::Model::Update < RailsOps::Operation::Model::Load
|
2
|
+
model_authorization_action :update
|
3
|
+
|
4
|
+
# As this operation might extend the model class, we need to make sure that
|
5
|
+
# the operation works using an extended 'copy' of the given model class.
|
6
|
+
def self.always_extend_model_class?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def model_authorization
|
11
|
+
return unless authorization_enabled?
|
12
|
+
|
13
|
+
unless load_model_authorization_action.nil?
|
14
|
+
authorize_model_with_authorize_only! load_model_authorization_action, model
|
15
|
+
end
|
16
|
+
|
17
|
+
unless model_authorization_action.nil?
|
18
|
+
authorize_model! model_authorization_action, model
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def build_model
|
23
|
+
super
|
24
|
+
build_nested_model_ops :update
|
25
|
+
assign_attributes
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform
|
29
|
+
save!
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Internal reference: #25564
|
2
|
+
module ActiveType
|
3
|
+
class TypeCaster
|
4
|
+
def type_cast_from_user(value)
|
5
|
+
# For some reason, Rails defines additional type casting logic
|
6
|
+
# outside the classes that have that responsibility.
|
7
|
+
case @type
|
8
|
+
when :integer
|
9
|
+
if value == ''
|
10
|
+
nil
|
11
|
+
else
|
12
|
+
native_type_cast_from_user(value)
|
13
|
+
end
|
14
|
+
when :timestamp, :datetime
|
15
|
+
time = native_type_cast_from_user(value)
|
16
|
+
if time && ActiveRecord::Base.time_zone_aware_attributes
|
17
|
+
time = ActiveSupport::TimeWithZone.new(nil, Time.zone, time)
|
18
|
+
end
|
19
|
+
time
|
20
|
+
when Integer.class
|
21
|
+
if value == ''
|
22
|
+
nil
|
23
|
+
else
|
24
|
+
value.to_i
|
25
|
+
end
|
26
|
+
else
|
27
|
+
native_type_cast_from_user(value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module NativeCasters
|
32
|
+
class DelegateToType
|
33
|
+
def initialize(type, connection)
|
34
|
+
# The specified type (e.g. "string") may not necessary match the
|
35
|
+
# native type ("varchar") expected by the connection adapter.
|
36
|
+
# PostgreSQL is one of these. Perform a translation if the adapter
|
37
|
+
# supports it (but don't turn a mysql boolean into a tinyint).
|
38
|
+
if !type.nil? && !(type == :boolean) && type.respond_to?(:to_sym) && connection.respond_to?(:native_database_types)
|
39
|
+
native_type = connection.native_database_types[type.try(:to_sym)]
|
40
|
+
if native_type && native_type[:name]
|
41
|
+
type = native_type[:name]
|
42
|
+
else
|
43
|
+
# unknown type, we just dont cast
|
44
|
+
type = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
@active_record_type = connection.lookup_cast_type(type)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module RailsOps
|
2
|
+
class Profiler
|
3
|
+
def self.profile(object_id, description = nil, &_block)
|
4
|
+
node = tstore_nodes[object_id] = ::RailsOps::Profiler::Node.new(object_id, description, tstore_current_parent)
|
5
|
+
self.tstore_current_parent = node
|
6
|
+
res = yield
|
7
|
+
self.tstore_current_parent = node.parent
|
8
|
+
node.finish_measure
|
9
|
+
res
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.time(descr = nil, &_block)
|
13
|
+
descr += ' - ' if descr
|
14
|
+
start = Time.now
|
15
|
+
res = yield
|
16
|
+
puts "#{descr}#{((Time.now - start).to_f * 1000).round(1)}ms elapsed.".magenta
|
17
|
+
res
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.forget_all
|
21
|
+
Thread.current[:rails_ops_profiler][:nodes] = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.forget(object_id)
|
25
|
+
tstore_nodes.delete(object_id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.node(object_id)
|
29
|
+
tstore_nodes[object_id] || fail("Unkown object_id #{object_id}.")
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.tstore_current_parent
|
33
|
+
Thread.current[:rails_ops_profiler] ||= {}
|
34
|
+
Thread.current[:rails_ops_profiler][:current_parent]
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.tstore_current_parent=(parent)
|
38
|
+
Thread.current[:rails_ops_profiler] ||= {}
|
39
|
+
Thread.current[:rails_ops_profiler][:current_parent] = parent
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.tstore_nodes
|
43
|
+
Thread.current[:rails_ops_profiler] ||= {}
|
44
|
+
Thread.current[:rails_ops_profiler][:nodes] ||= {}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module RailsOps
|
2
|
+
class Profiler::Node
|
3
|
+
def initialize(object_id, description = nil, parent = nil)
|
4
|
+
@object_id = object_id
|
5
|
+
@description = description
|
6
|
+
@parent = parent
|
7
|
+
parent&.add_child(self)
|
8
|
+
@children = []
|
9
|
+
@t_start = Time.now
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :parent
|
13
|
+
attr_reader :description
|
14
|
+
|
15
|
+
def finish_measure
|
16
|
+
@t_stop = Time.now
|
17
|
+
end
|
18
|
+
|
19
|
+
def t_self
|
20
|
+
t_total - t_kids
|
21
|
+
end
|
22
|
+
|
23
|
+
def t_kids
|
24
|
+
@children.map(&:t_total).inject(:+) || 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def t_total
|
28
|
+
fail "Measure for object_id #{@object_id} (#{@description}) is not finished." unless @t_stop
|
29
|
+
(@t_stop - @t_start).to_f
|
30
|
+
end
|
31
|
+
|
32
|
+
def t_self_s
|
33
|
+
t_self
|
34
|
+
end
|
35
|
+
|
36
|
+
def t_kids_s
|
37
|
+
t_kids
|
38
|
+
end
|
39
|
+
|
40
|
+
def t_total_s
|
41
|
+
t_total
|
42
|
+
end
|
43
|
+
|
44
|
+
def t_self_ms
|
45
|
+
t_self * 1000
|
46
|
+
end
|
47
|
+
|
48
|
+
def t_kids_ms
|
49
|
+
t_kids * 1000
|
50
|
+
end
|
51
|
+
|
52
|
+
def t_total_ms
|
53
|
+
t_total * 1000
|
54
|
+
end
|
55
|
+
|
56
|
+
def free
|
57
|
+
::RailsOps::Profiler.forget(@object_id) unless parent
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_child(child)
|
61
|
+
@children << child
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|