nandi 0.9.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.
- checksums.yaml +7 -0
- data/README.md +477 -0
- data/Rakefile +8 -0
- data/exe/nandi-enforce +36 -0
- data/lib/generators/nandi/check_constraint/USAGE +19 -0
- data/lib/generators/nandi/check_constraint/check_constraint_generator.rb +52 -0
- data/lib/generators/nandi/check_constraint/templates/add_check_constraint.rb +15 -0
- data/lib/generators/nandi/check_constraint/templates/validate_check_constraint.rb +9 -0
- data/lib/generators/nandi/compile/USAGE +19 -0
- data/lib/generators/nandi/compile/compile_generator.rb +62 -0
- data/lib/generators/nandi/foreign_key/USAGE +47 -0
- data/lib/generators/nandi/foreign_key/foreign_key_generator.rb +91 -0
- data/lib/generators/nandi/foreign_key/templates/add_foreign_key.rb +13 -0
- data/lib/generators/nandi/foreign_key/templates/add_reference.rb +11 -0
- data/lib/generators/nandi/foreign_key/templates/validate_foreign_key.rb +9 -0
- data/lib/generators/nandi/migration/USAGE +9 -0
- data/lib/generators/nandi/migration/migration_generator.rb +24 -0
- data/lib/generators/nandi/migration/templates/migration.rb +13 -0
- data/lib/generators/nandi/not_null_check/USAGE +19 -0
- data/lib/generators/nandi/not_null_check/not_null_check_generator.rb +56 -0
- data/lib/generators/nandi/not_null_check/templates/add_not_null_check.rb +11 -0
- data/lib/generators/nandi/not_null_check/templates/validate_not_null_check.rb +9 -0
- data/lib/nandi.rb +35 -0
- data/lib/nandi/compiled_migration.rb +86 -0
- data/lib/nandi/config.rb +126 -0
- data/lib/nandi/file_diff.rb +32 -0
- data/lib/nandi/file_matcher.rb +72 -0
- data/lib/nandi/formatting.rb +79 -0
- data/lib/nandi/instructions.rb +19 -0
- data/lib/nandi/instructions/add_check_constraint.rb +23 -0
- data/lib/nandi/instructions/add_column.rb +24 -0
- data/lib/nandi/instructions/add_foreign_key.rb +40 -0
- data/lib/nandi/instructions/add_index.rb +50 -0
- data/lib/nandi/instructions/change_column_default.rb +23 -0
- data/lib/nandi/instructions/create_table.rb +83 -0
- data/lib/nandi/instructions/drop_constraint.rb +22 -0
- data/lib/nandi/instructions/drop_table.rb +21 -0
- data/lib/nandi/instructions/irreversible_migration.rb +15 -0
- data/lib/nandi/instructions/remove_column.rb +23 -0
- data/lib/nandi/instructions/remove_index.rb +41 -0
- data/lib/nandi/instructions/remove_not_null_constraint.rb +22 -0
- data/lib/nandi/instructions/validate_constraint.rb +22 -0
- data/lib/nandi/lockfile.rb +58 -0
- data/lib/nandi/migration.rb +363 -0
- data/lib/nandi/renderers.rb +7 -0
- data/lib/nandi/renderers/active_record.rb +13 -0
- data/lib/nandi/renderers/active_record/generate.rb +59 -0
- data/lib/nandi/renderers/active_record/instructions.rb +134 -0
- data/lib/nandi/safe_migration_enforcer.rb +143 -0
- data/lib/nandi/timeout_policies.rb +38 -0
- data/lib/nandi/timeout_policies/access_exclusive.rb +54 -0
- data/lib/nandi/timeout_policies/concurrent.rb +64 -0
- data/lib/nandi/validation.rb +10 -0
- data/lib/nandi/validation/add_column_validator.rb +43 -0
- data/lib/nandi/validation/each_validator.rb +32 -0
- data/lib/nandi/validation/failure_helpers.rb +35 -0
- data/lib/nandi/validation/remove_index_validator.rb +30 -0
- data/lib/nandi/validation/result.rb +30 -0
- data/lib/nandi/validation/timeout_validator.rb +37 -0
- data/lib/nandi/validator.rb +102 -0
- data/lib/nandi/version.rb +5 -0
- data/lib/templates/nandi/renderers/active_record/generate/show.rb.erb +27 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_check_constraint/show.rb.erb +7 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_column/show.rb.erb +6 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_foreign_key/show.rb.erb +5 -0
- data/lib/templates/nandi/renderers/active_record/instructions/add_index/show.rb.erb +5 -0
- data/lib/templates/nandi/renderers/active_record/instructions/change_column_default/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/create_table/show.rb.erb +8 -0
- data/lib/templates/nandi/renderers/active_record/instructions/drop_constraint/show.rb.erb +4 -0
- data/lib/templates/nandi/renderers/active_record/instructions/drop_table/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/irreversible_migration/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/remove_column/show.rb.erb +5 -0
- data/lib/templates/nandi/renderers/active_record/instructions/remove_index/show.rb.erb +4 -0
- data/lib/templates/nandi/renderers/active_record/instructions/remove_not_null_constraint/show.rb.erb +1 -0
- data/lib/templates/nandi/renderers/active_record/instructions/validate_constraint/show.rb.erb +3 -0
- metadata +320 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nandi
|
4
|
+
module Instructions
|
5
|
+
class RemoveNotNullConstraint
|
6
|
+
attr_reader :table, :column
|
7
|
+
|
8
|
+
def initialize(table:, column:)
|
9
|
+
@table = table
|
10
|
+
@column = column
|
11
|
+
end
|
12
|
+
|
13
|
+
def procedure
|
14
|
+
:remove_not_null_constraint
|
15
|
+
end
|
16
|
+
|
17
|
+
def lock
|
18
|
+
Nandi::Migration::LockWeights::ACCESS_EXCLUSIVE
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nandi
|
4
|
+
module Instructions
|
5
|
+
class ValidateConstraint
|
6
|
+
attr_reader :table, :name
|
7
|
+
|
8
|
+
def initialize(table:, name:)
|
9
|
+
@table = table
|
10
|
+
@name = name
|
11
|
+
end
|
12
|
+
|
13
|
+
def lock
|
14
|
+
Nandi::Migration::LockWeights::SHARE
|
15
|
+
end
|
16
|
+
|
17
|
+
def procedure
|
18
|
+
:validate_constraint
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
|
5
|
+
module Nandi
|
6
|
+
class Lockfile
|
7
|
+
class << self
|
8
|
+
def file_present?
|
9
|
+
File.exist?(path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def create!
|
13
|
+
return if file_present?
|
14
|
+
|
15
|
+
File.write(path, {}.to_yaml)
|
16
|
+
end
|
17
|
+
|
18
|
+
def add(file_name:, source_digest:, compiled_digest:)
|
19
|
+
load!
|
20
|
+
|
21
|
+
lockfile[file_name] = {
|
22
|
+
source_digest: source_digest,
|
23
|
+
compiled_digest: compiled_digest,
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(file_name)
|
28
|
+
load!
|
29
|
+
|
30
|
+
{
|
31
|
+
source_digest: lockfile.dig(file_name, :source_digest),
|
32
|
+
compiled_digest: lockfile.dig(file_name, :compiled_digest),
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def load!
|
37
|
+
return lockfile if lockfile
|
38
|
+
|
39
|
+
Nandi::Lockfile.create! unless Nandi::Lockfile.file_present?
|
40
|
+
|
41
|
+
@lockfile = YAML.safe_load(File.read(path)).with_indifferent_access
|
42
|
+
end
|
43
|
+
|
44
|
+
def persist!
|
45
|
+
File.write(path, lockfile.to_h.deep_stringify_keys.to_yaml)
|
46
|
+
end
|
47
|
+
|
48
|
+
def path
|
49
|
+
File.join(
|
50
|
+
Nandi.config.lockfile_directory,
|
51
|
+
".nandilock.yml",
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_accessor :lockfile
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,363 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "nandi/instructions"
|
4
|
+
require "nandi/validator"
|
5
|
+
require "nandi/validation/failure_helpers"
|
6
|
+
|
7
|
+
module Nandi
|
8
|
+
# @abstract A migration must implement #up (the forward migration), and may
|
9
|
+
# also implement #down (the rollback sequence).
|
10
|
+
# The base class for migrations; Nandi's equivalent of ActiveRecord::Migration.
|
11
|
+
# All the statements in the migration are statically analysed together to rule
|
12
|
+
# out migrations with a high risk of causing availability issues. Additionally,
|
13
|
+
# our implementations of some statements will rule out certain common footguns
|
14
|
+
# (for example, creating an index without using the `CONCURRENTLY` parameter.)
|
15
|
+
# @example
|
16
|
+
# class CreateWidgetsTable < Nandi::Migration
|
17
|
+
# def up
|
18
|
+
# create_table :widgets do |t|
|
19
|
+
# t.column :weight, :number
|
20
|
+
# t.column :name, :text, default: "Unknown widget"
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def down
|
25
|
+
# drop_table :widgets
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
class Migration
|
29
|
+
include Nandi::Validation::FailureHelpers
|
30
|
+
|
31
|
+
module LockWeights
|
32
|
+
ACCESS_EXCLUSIVE = 1
|
33
|
+
SHARE = 0
|
34
|
+
end
|
35
|
+
|
36
|
+
class InstructionSet < SimpleDelegator
|
37
|
+
def strictest_lock
|
38
|
+
return LockWeights::SHARE if empty?
|
39
|
+
|
40
|
+
map { |i| i.respond_to?(:lock) ? i.lock : LockWeights::ACCESS_EXCLUSIVE }.max
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class << self
|
45
|
+
attr_reader :lock_timeout, :statement_timeout
|
46
|
+
# For sake both of correspondence with Postgres syntax and familiarity
|
47
|
+
# with activerecord-safe_migrations's identically named macros, we
|
48
|
+
# disable this cop.
|
49
|
+
|
50
|
+
# rubocop:disable Naming/AccessorMethodName
|
51
|
+
|
52
|
+
# Override the default lock timeout for the duration of the migration.
|
53
|
+
# This may be helpful when making changes to very busy tables, when a
|
54
|
+
# lock is less likely to be immediately available.
|
55
|
+
# @param timeout [Integer] New lock timeout in ms
|
56
|
+
def set_lock_timeout(timeout)
|
57
|
+
@lock_timeout = timeout
|
58
|
+
end
|
59
|
+
|
60
|
+
# Override the default statement timeout for the duration of the migration.
|
61
|
+
# This may be helpful when making changes that are likely to take a lot
|
62
|
+
# of time, like adding a new index on a large table.
|
63
|
+
# @param timeout [Integer] New lock timeout in ms
|
64
|
+
def set_statement_timeout(timeout)
|
65
|
+
@statement_timeout = timeout
|
66
|
+
end
|
67
|
+
# rubocop:enable Naming/AccessorMethodName
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param validator [Nandi::Validator]
|
71
|
+
def initialize(validator)
|
72
|
+
@validator = validator
|
73
|
+
@instructions = Hash.new { |h, k| h[k] = InstructionSet.new([]) }
|
74
|
+
validate
|
75
|
+
end
|
76
|
+
|
77
|
+
# @api private
|
78
|
+
def up_instructions
|
79
|
+
compile_instructions(:up)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @api private
|
83
|
+
def down_instructions
|
84
|
+
compile_instructions(:down)
|
85
|
+
end
|
86
|
+
|
87
|
+
# The current lock timeout.
|
88
|
+
def lock_timeout
|
89
|
+
self.class.lock_timeout || default_lock_timeout
|
90
|
+
end
|
91
|
+
|
92
|
+
# The current statement timeout.
|
93
|
+
def statement_timeout
|
94
|
+
self.class.statement_timeout || default_statement_timeout
|
95
|
+
end
|
96
|
+
|
97
|
+
# @api private
|
98
|
+
def strictest_lock
|
99
|
+
@instructions.values.map(&:strictest_lock).max
|
100
|
+
end
|
101
|
+
|
102
|
+
# @abstract
|
103
|
+
def up
|
104
|
+
raise NotImplementedError
|
105
|
+
end
|
106
|
+
|
107
|
+
def down; end
|
108
|
+
|
109
|
+
# Adds a new index to the database.
|
110
|
+
#
|
111
|
+
# Nandi will:
|
112
|
+
# * add the `CONCURRENTLY` option, which means the change takes a less
|
113
|
+
# restrictive lock at the cost of not running in a DDL transaction
|
114
|
+
# * use the `BTREE` index type which is the safest to create.
|
115
|
+
#
|
116
|
+
# Because index creation is particularly failure-prone, and because
|
117
|
+
# we cannot run in a transaction and therefore risk partially applied
|
118
|
+
# migrations that (in a Rails environment) require manual intervention,
|
119
|
+
# Nandi Validates that, if there is a add_index statement in the
|
120
|
+
# migration, it must be the only statement.
|
121
|
+
# @param table [Symbol, String] The name of the table to add the index to
|
122
|
+
# @param fields [Symbol, String, Array] The field or fields to use in the
|
123
|
+
# index
|
124
|
+
# @param kwargs [Hash] Arbitrary options to pass to the backend adapter.
|
125
|
+
# Attempts to remove `CONCURRENTLY` or change the index type will be ignored.
|
126
|
+
def add_index(table, fields, **kwargs)
|
127
|
+
current_instructions << Instructions::AddIndex.new(
|
128
|
+
**kwargs,
|
129
|
+
table: table,
|
130
|
+
fields: fields,
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Drop an index from the database.
|
135
|
+
#
|
136
|
+
# Nandi will add the `CONCURRENTLY` option, which means the change
|
137
|
+
# takes a less restrictive lock at the cost of not running in a DDL
|
138
|
+
# transaction.
|
139
|
+
#
|
140
|
+
# Because we cannot run in a transaction and therefore risk partially
|
141
|
+
# applied migrations that (in a Rails environment) require manual
|
142
|
+
# intervention, Nandi Validates that, if there is a remove_index statement
|
143
|
+
# in the migration, it must be the only statement.
|
144
|
+
# @param table [Symbol, String] The name of the table to add the index to
|
145
|
+
# @param target [Symbol, String, Array, Hash] This can be either the field (or
|
146
|
+
# array of fields) in the index to be dropped, or a hash of options, which
|
147
|
+
# must include either a `column` key (which is the same: a field or list
|
148
|
+
# of fields) or a `name` key, which is the name of the index to be dropped.
|
149
|
+
def remove_index(table, target)
|
150
|
+
current_instructions << Instructions::RemoveIndex.new(table: table, field: target)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Creates a new table. Yields a ColumnsReader object as a block, to allow adding
|
154
|
+
# columns.
|
155
|
+
# @example
|
156
|
+
# create_table :widgets do |t|
|
157
|
+
# t.text :foo, default: true
|
158
|
+
# end
|
159
|
+
# @param table [String, Symbol] The name of the new table
|
160
|
+
# @yieldparam columns_reader [Nandi::Instructions::CreateTable::ColumnsReader]
|
161
|
+
def create_table(table, **kwargs, &block)
|
162
|
+
current_instructions << Instructions::CreateTable.new(
|
163
|
+
**kwargs,
|
164
|
+
table: table,
|
165
|
+
columns_block: block,
|
166
|
+
)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Drops an existing table
|
170
|
+
# @param table [String, Symbol] The name of the table to drop.
|
171
|
+
def drop_table(table)
|
172
|
+
current_instructions << Instructions::DropTable.new(table: table)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Adds a new column. Nandi will explicitly set the column to be NULL,
|
176
|
+
# as validating a new NOT NULL constraint can be very expensive on large
|
177
|
+
# tables and cause availability issues.
|
178
|
+
# @param table [Symbol, String] The name of the table to add the column to
|
179
|
+
# @param name [Symbol, String] The name of the column
|
180
|
+
# @param type [Symbol, String] The type of the column
|
181
|
+
# @param kwargs [Hash] Arbitrary options to be passed to the backend.
|
182
|
+
def add_column(table, name, type, **kwargs)
|
183
|
+
current_instructions << Instructions::AddColumn.new(
|
184
|
+
table: table,
|
185
|
+
name: name,
|
186
|
+
type: type,
|
187
|
+
**kwargs,
|
188
|
+
)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Remove an existing column.
|
192
|
+
# @param table [Symbol, String] The name of the table to remove the column
|
193
|
+
# from.
|
194
|
+
# @param name [Symbol, String] The name of the column
|
195
|
+
# @param extra_args [Hash] Arbitrary options to be passed to the backend.
|
196
|
+
def remove_column(table, name, **extra_args)
|
197
|
+
current_instructions << Instructions::RemoveColumn.new(
|
198
|
+
**extra_args,
|
199
|
+
table: table,
|
200
|
+
name: name,
|
201
|
+
)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Add a foreign key constraint. The generated SQL will include the NOT VALID
|
205
|
+
# parameter, which will prevent immediate validation of the constraint, which
|
206
|
+
# locks the target table for writes potentially for a long time. Use the separate
|
207
|
+
# #validate_constraint method, in a separate migration; this only takes a row-level
|
208
|
+
# lock as it scans through.
|
209
|
+
# @param table [Symbol, String] The name of the table with the reference column
|
210
|
+
# @param target [Symbol, String] The name of the referenced table
|
211
|
+
# @param column [Symbol, String] The name of the reference column. If omitted, will
|
212
|
+
# default to the singular of target + "_id"
|
213
|
+
# @param name [Symbol, String] The name of the constraint to create. Defaults to
|
214
|
+
# table_target_fk
|
215
|
+
def add_foreign_key(table, target, column: nil, name: nil)
|
216
|
+
current_instructions << Instructions::AddForeignKey.new(
|
217
|
+
table: table,
|
218
|
+
target: target,
|
219
|
+
column: column,
|
220
|
+
name: name,
|
221
|
+
)
|
222
|
+
end
|
223
|
+
|
224
|
+
# Add a check constraint, in the NOT VALID state.
|
225
|
+
# @param table [Symbol, String] The name of the table with the column
|
226
|
+
# @param name [Symbol, String] The name of the constraint to create
|
227
|
+
# @param check [Symbol, String] The predicate to check
|
228
|
+
def add_check_constraint(table, name, check)
|
229
|
+
current_instructions << Instructions::AddCheckConstraint.new(
|
230
|
+
table: table,
|
231
|
+
name: name,
|
232
|
+
check: check,
|
233
|
+
)
|
234
|
+
end
|
235
|
+
|
236
|
+
# Validates an existing foreign key constraint.
|
237
|
+
# @param table [Symbol, String] The name of the table with the constraint
|
238
|
+
# @param name [Symbol, String] The name of the constraint
|
239
|
+
def validate_constraint(table, name)
|
240
|
+
current_instructions << Instructions::ValidateConstraint.new(
|
241
|
+
table: table,
|
242
|
+
name: name,
|
243
|
+
)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Drops an existing constraint.
|
247
|
+
# @param table [Symbol, String] The name of the table with the constraint
|
248
|
+
# @param name [Symbol, String] The name of the constraint
|
249
|
+
def drop_constraint(table, name)
|
250
|
+
current_instructions << Instructions::DropConstraint.new(
|
251
|
+
table: table,
|
252
|
+
name: name,
|
253
|
+
)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Drops an existing NOT NULL constraint. Please note that this migration is
|
257
|
+
# not safely reversible; to enforce NOT NULL like behaviour, use a CHECK
|
258
|
+
# constraint and validate it in a separate migration.
|
259
|
+
# @param table [Symbol, String] The name of the table with the constraint
|
260
|
+
# @param column [Symbol, String] The name of the column to remove NOT NULL
|
261
|
+
# constraint from
|
262
|
+
def remove_not_null_constraint(table, column)
|
263
|
+
current_instructions << Instructions::RemoveNotNullConstraint.new(
|
264
|
+
table: table,
|
265
|
+
column: column,
|
266
|
+
)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Changes the default value for this column when new rows are inserted into
|
270
|
+
# the table.
|
271
|
+
# @param table [Symbol, String] The name of the table with the column
|
272
|
+
# @param column [Symbol, String] The name of the column to change
|
273
|
+
# @param value [Object] The new default value
|
274
|
+
def change_column_default(table, column, value)
|
275
|
+
current_instructions << Instructions::ChangeColumnDefault.new(
|
276
|
+
table: table,
|
277
|
+
column: column,
|
278
|
+
value: value,
|
279
|
+
)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Raises an `ActiveRecord::IrreversibleMigration` error for use in
|
283
|
+
# irreversible migrations
|
284
|
+
def irreversible_migration
|
285
|
+
current_instructions << Instructions::IrreversibleMigration.new
|
286
|
+
end
|
287
|
+
|
288
|
+
# @api private
|
289
|
+
def compile_instructions(direction)
|
290
|
+
@direction = direction
|
291
|
+
|
292
|
+
public_send(direction) unless current_instructions.any?
|
293
|
+
|
294
|
+
current_instructions
|
295
|
+
end
|
296
|
+
|
297
|
+
# @api private
|
298
|
+
def validate
|
299
|
+
validator.call(self)
|
300
|
+
rescue NotImplementedError => e
|
301
|
+
Validation::Result.new << failure(e.message)
|
302
|
+
end
|
303
|
+
|
304
|
+
def disable_lock_timeout?
|
305
|
+
if self.class.lock_timeout.nil?
|
306
|
+
strictest_lock == LockWeights::SHARE
|
307
|
+
else
|
308
|
+
false
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def disable_statement_timeout?
|
313
|
+
if self.class.statement_timeout.nil?
|
314
|
+
strictest_lock == LockWeights::SHARE
|
315
|
+
else
|
316
|
+
false
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def name
|
321
|
+
self.class.name
|
322
|
+
end
|
323
|
+
|
324
|
+
def respond_to_missing?(name)
|
325
|
+
Nandi.config.custom_methods.key?(name) || super
|
326
|
+
end
|
327
|
+
|
328
|
+
def mixins
|
329
|
+
(up_instructions + down_instructions).inject([]) do |mixins, i|
|
330
|
+
i.respond_to?(:mixins) ? [*mixins, *i.mixins] : mixins
|
331
|
+
end.uniq
|
332
|
+
end
|
333
|
+
|
334
|
+
def method_missing(name, *args, &block)
|
335
|
+
if Nandi.config.custom_methods.key?(name)
|
336
|
+
invoke_custom_method(name, *args, &block)
|
337
|
+
else
|
338
|
+
super
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
private
|
343
|
+
|
344
|
+
attr_reader :validator
|
345
|
+
|
346
|
+
def current_instructions
|
347
|
+
@instructions[@direction]
|
348
|
+
end
|
349
|
+
|
350
|
+
def default_statement_timeout
|
351
|
+
Nandi.config.access_exclusive_statement_timeout
|
352
|
+
end
|
353
|
+
|
354
|
+
def default_lock_timeout
|
355
|
+
Nandi.config.access_exclusive_lock_timeout
|
356
|
+
end
|
357
|
+
|
358
|
+
def invoke_custom_method(name, *args, &block)
|
359
|
+
klass = Nandi.config.custom_methods[name]
|
360
|
+
current_instructions << klass.new(*args, &block)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|