Class: HairTrigger::Builder
- Inherits:
-
Object
- Object
- HairTrigger::Builder
show all
- Defined in:
- lib/hair_trigger/builder.rb
Defined Under Namespace
Classes: DeclarationError, GenerationError
Class Attribute Summary collapse
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
Constructor Details
#initialize(name = nil, options = {}) ⇒ Builder
Returns a new instance of Builder.
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
# File 'lib/hair_trigger/builder.rb', line 12
def initialize(name = nil, options = {})
@adapter = options[:adapter]
@compatibility = options.delete(:compatibility) || self.class.compatibility
@options = {}
@chained_calls = []
@errors = []
@warnings = []
set_name(name) if name
{:timing => :after, :for_each => :row}.update(options).each do |key, value|
if respond_to?("set_#{key}")
send("set_#{key}", *Array[value])
else
@options[key] = value
end
end
end
|
Class Attribute Details
.base_compatibility ⇒ Object
533
534
535
|
# File 'lib/hair_trigger/builder.rb', line 533
def base_compatibility
@base_compatibility ||= 0
end
|
.show_warnings ⇒ Object
528
529
530
531
|
# File 'lib/hair_trigger/builder.rb', line 528
def show_warnings
@show_warnings = true if @show_warnings.nil?
@show_warnings
end
|
.tab_spacing ⇒ Object
524
525
526
|
# File 'lib/hair_trigger/builder.rb', line 524
def tab_spacing
@tab_spacing ||= 4
end
|
Instance Attribute Details
#options ⇒ Object
Returns the value of attribute options.
8
9
10
|
# File 'lib/hair_trigger/builder.rb', line 8
def options
@options
end
|
#prepared_actions ⇒ Object
after delayed interpolation
10
11
12
|
# File 'lib/hair_trigger/builder.rb', line 10
def prepared_actions
@prepared_actions
end
|
#prepared_where ⇒ Object
after delayed interpolation
10
11
12
|
# File 'lib/hair_trigger/builder.rb', line 10
def prepared_where
@prepared_where
end
|
#triggers ⇒ Object
nil unless this is a trigger group
9
10
11
|
# File 'lib/hair_trigger/builder.rb', line 9
def triggers
@triggers
end
|
Class Method Details
.chainable_methods(*methods) ⇒ Object
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
# File 'lib/hair_trigger/builder.rb', line 136
def self.chainable_methods(*methods)
methods.each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
alias #{method}_orig #{method}
def #{method}(*args)
@chained_calls << :#{method}
if @triggers || @trigger_group
@errors << ["mysql doesn't support #{method} within a trigger group", :mysql] unless [:name, :where, :all, :of].include?(:#{method})
end
set_#{method}(*args, &(block_given? ? Proc.new : nil))
end
def set_#{method}(*args)
if @triggers # i.e. each time we say t.something within a trigger group block
@chained_calls.pop # the subtrigger will get this, we don't need it
@chained_calls = @chained_calls.uniq
@triggers << trigger = clone
trigger.#{method}(*args, &(block_given? ? Proc.new : nil))
else
#{method}_orig(*args)
maybe_execute(&Proc.new) if block_given?
self
end
end
METHOD
end
end
|
.compatibility ⇒ Object
537
538
539
540
541
542
543
544
545
546
547
|
# File 'lib/hair_trigger/builder.rb', line 537
def compatibility
@compatibility ||= begin
if HairTrigger::VERSION <= "0.1.3"
0 else
1 end
end
end
|
Instance Method Details
#<=>(other) ⇒ Object
265
266
267
268
269
|
# File 'lib/hair_trigger/builder.rb', line 265
def <=>(other)
ret = prepared_name <=> other.prepared_name
return ret unless ret == 0
hash <=> other.hash
end
|
#==(other) ⇒ Object
271
272
273
|
# File 'lib/hair_trigger/builder.rb', line 271
def ==(other)
components == other.components
end
|
#after(*events) ⇒ Object
67
68
69
70
|
# File 'lib/hair_trigger/builder.rb', line 67
def after(*events)
set_timing(:after)
set_events(*events)
end
|
#all ⇒ Object
noop, just a way you can pass a block within a trigger group
90
91
|
# File 'lib/hair_trigger/builder.rb', line 90
def all
end
|
#all_names ⇒ Object
126
127
128
|
# File 'lib/hair_trigger/builder.rb', line 126
def all_names
[prepared_name] + (@triggers ? @triggers.map(&:prepared_name) : [])
end
|
#all_triggers(include_self = true) ⇒ Object
130
131
132
133
134
|
# File 'lib/hair_trigger/builder.rb', line 130
def all_triggers(include_self = true)
triggers = []
triggers << self if include_self
(@triggers || []).map(&:all_triggers).inject(triggers, &:concat)
end
|
#before(*events) ⇒ Object
62
63
64
65
|
# File 'lib/hair_trigger/builder.rb', line 62
def before(*events)
set_timing(:before)
set_events(*events)
end
|
#change_clause(column) ⇒ Object
189
190
191
|
# File 'lib/hair_trigger/builder.rb', line 189
def change_clause(column)
"NEW.#{column} <> OLD.#{column} OR (NEW.#{column} IS NULL) <> (OLD.#{column} IS NULL)"
end
|
#components ⇒ Object
284
285
286
|
# File 'lib/hair_trigger/builder.rb', line 284
def components
[@options, @prepared_actions, @explicit_where, @triggers, @compatibility]
end
|
#create_grouped_trigger? ⇒ Boolean
164
165
166
|
# File 'lib/hair_trigger/builder.rb', line 164
def create_grouped_trigger?
adapter_name == :mysql
end
|
#declare(declarations) ⇒ Object
85
86
87
|
# File 'lib/hair_trigger/builder.rb', line 85
def declare(declarations)
options[:declarations] = declarations
end
|
#drop_triggers ⇒ Object
42
43
44
|
# File 'lib/hair_trigger/builder.rb', line 42
def drop_triggers
all_names.map{ |name| self.class.new(name, {:table => options[:table], :drop => true}) }
end
|
#eql?(other) ⇒ Boolean
275
276
277
|
# File 'lib/hair_trigger/builder.rb', line 275
def eql?(other)
other.is_a?(HairTrigger::Builder) && self == other
end
|
#errors ⇒ Object
288
289
290
|
# File 'lib/hair_trigger/builder.rb', line 288
def errors
(@triggers || []).map(&:errors).inject(@errors, &:+)
end
|
#events(*events) ⇒ Object
109
110
111
112
113
114
115
116
|
# File 'lib/hair_trigger/builder.rb', line 109
def events(*events)
events << :insert if events.delete(:create)
events << :delete if events.delete(:destroy)
raise DeclarationError, "invalid events" unless events & [:insert, :update, :delete, :truncate] == events
@errors << ["sqlite and mysql triggers may not be shared by multiple actions", :mysql, :sqlite] if events.size > 1
@errors << ["sqlite and mysql do not support truncate triggers", :mysql, :sqlite] if events.include?(:truncate)
options[:events] = events.map{ |e| e.to_s.upcase }
end
|
#for_each(for_each) ⇒ Object
56
57
58
59
60
|
# File 'lib/hair_trigger/builder.rb', line 56
def for_each(for_each)
@errors << ["sqlite and mysql don't support FOR EACH STATEMENT triggers", :sqlite, :mysql] if for_each == :statement
raise DeclarationError, "invalid for_each" unless [:row, :statement].include?(for_each)
options[:for_each] = for_each.to_s.upcase
end
|
#generate(validate = true) ⇒ Object
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
|
# File 'lib/hair_trigger/builder.rb', line 210
def generate(validate = true)
validate!(@trigger_group ? :both : :down) if validate
return @triggers.map{ |t| t.generate(false) }.flatten if @triggers && !create_grouped_trigger?
prepare!
raise GenerationError, "need to specify the table" unless options[:table]
if options[:drop]
generate_drop_trigger
else
raise GenerationError, "no actions specified" if @triggers && create_grouped_trigger? ? @triggers.any?{ |t| t.raw_actions.nil? } : raw_actions.nil?
raise GenerationError, "need to specify the event(s) (:insert, :update, :delete)" if !options[:events] || options[:events].empty?
raise GenerationError, "need to specify the timing (:before/:after)" unless options[:timing]
[generate_drop_trigger] +
[case adapter_name
when :sqlite
generate_trigger_sqlite
when :mysql
generate_trigger_mysql
when :postgresql, :postgis
generate_trigger_postgresql
else
raise GenerationError, "don't know how to build #{adapter_name} triggers yet"
end].flatten
end
end
|
#hash ⇒ Object
279
280
281
282
|
# File 'lib/hair_trigger/builder.rb', line 279
def hash
prepare!
components.hash
end
|
#initialize_copy(other) ⇒ Object
29
30
31
32
33
34
35
36
37
38
39
40
|
# File 'lib/hair_trigger/builder.rb', line 29
def initialize_copy(other)
@trigger_group = other
@triggers = nil
@chained_calls = []
@errors = []
@warnings = []
@options = @options.dup
@options.delete(:name) @options.each do |key, value|
@options[key] = value.dup rescue value
end
end
|
#name(name) ⇒ Object
46
47
48
49
|
# File 'lib/hair_trigger/builder.rb', line 46
def name(name)
@errors << ["trigger name cannot exceed 63 for postgres", :postgresql] if name.to_s.size > 63
options[:name] = name.to_s
end
|
#nowrap(flag = true) ⇒ Object
76
77
78
|
# File 'lib/hair_trigger/builder.rb', line 76
def nowrap(flag = true)
options[:nowrap] = flag
end
|
#of(*columns) ⇒ Object
80
81
82
83
|
# File 'lib/hair_trigger/builder.rb', line 80
def of(*columns)
raise DeclarationError, "`of' requested, but no columns specified" unless columns.present?
options[:of] = columns
end
|
#on(table) ⇒ Object
51
52
53
54
|
# File 'lib/hair_trigger/builder.rb', line 51
def on(table)
raise DeclarationError, "table has already been specified" if options[:table]
options[:table] = table.to_s
end
|
#prepare! ⇒ Object
168
169
170
171
172
173
174
175
176
177
|
# File 'lib/hair_trigger/builder.rb', line 168
def prepare!
@triggers.each(&:prepare!) if @triggers
prepare_where!
if @actions
@prepared_actions = @actions.is_a?(Hash) ?
@actions.inject({}){ |hash, (key, value)| hash[key] = interpolate(value).rstrip; hash } :
interpolate(@actions).rstrip
end
all_names end
|
#prepare_where! ⇒ Object
179
180
181
182
183
184
185
186
187
|
# File 'lib/hair_trigger/builder.rb', line 179
def prepare_where!
parts = []
parts << @explicit_where = options[:where] = interpolate(options[:where]) if options[:where]
parts << options[:of].map{ |col| change_clause(col) }.join(" OR ") if options[:of] && !supports_of?
if parts.present?
parts.map!{ |part| "(" + part + ")" } if parts.size > 1
@prepared_where = parts.join(" AND ")
end
end
|
#prepared_name ⇒ Object
122
123
124
|
# File 'lib/hair_trigger/builder.rb', line 122
def prepared_name
@prepared_name ||= options[:name] ||= infer_name
end
|
#raw_actions ⇒ Object
118
119
120
|
# File 'lib/hair_trigger/builder.rb', line 118
def raw_actions
@raw_actions ||= prepared_actions.is_a?(Hash) ? prepared_actions[adapter_name] || prepared_actions[:default] : prepared_actions
end
|
#security(user) ⇒ Object
93
94
95
96
97
98
99
100
101
102
|
# File 'lib/hair_trigger/builder.rb', line 93
def security(user)
unless [:invoker, :definer].include?(user) || user.to_s =~ /\A'[^']+'@'[^']+'\z/ || user.to_s.downcase =~ /\Acurrent_user(\(\))?\z/
raise DeclarationError, "trigger security should be :invoker, :definer, CURRENT_USER, or a valid user (e.g. 'user'@'host')"
end
@errors << ["sqlite doesn't support trigger security", :sqlite]
@errors << ["postgresql doesn't support arbitrary users for trigger security", :postgresql] unless [:definer, :invoker].include?(user)
@errors << ["mysql doesn't support invoker trigger security", :mysql] if user == :invoker
options[:security] = user
end
|
#timing(timing) ⇒ Object
104
105
106
107
|
# File 'lib/hair_trigger/builder.rb', line 104
def timing(timing)
raise DeclarationError, "invalid timing" unless [:before, :after].include?(timing)
options[:timing] = timing.to_s.upcase
end
|
#to_ruby(indent = '', always_generated = true) ⇒ Object
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
|
# File 'lib/hair_trigger/builder.rb', line 237
def to_ruby(indent = '', always_generated = true)
prepare!
if options[:drop]
str = "#{indent}drop_trigger(#{prepared_name.inspect}, #{options[:table].inspect}"
str << ", :generated => true" if always_generated || options[:generated]
str << ")"
else
if @trigger_group
str = "t." + chained_calls_to_ruby + " do\n"
str << actions_to_ruby("#{indent} ") + "\n"
str << "#{indent}end"
else
str = "#{indent}create_trigger(#{prepared_name.inspect}"
str << ", :generated => true" if always_generated || options[:generated]
str << ", :compatibility => #{@compatibility}"
str << ").\n#{indent} " + chained_calls_to_ruby(".\n#{indent} ")
if @triggers
str << " do |t|\n"
str << "#{indent} " + @triggers.map{ |t| t.to_ruby("#{indent} ") }.join("\n\n#{indent} ") + "\n"
else
str << " do\n"
str << actions_to_ruby("#{indent} ") + "\n"
end
str << "#{indent}end"
end
end
end
|
#validate!(direction = :down) ⇒ Object
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
|
# File 'lib/hair_trigger/builder.rb', line 193
def validate!(direction = :down)
@errors.each do |(error, *adapters)|
raise GenerationError, error if adapters.include?(adapter_name)
$stderr.puts "WARNING: " + error if self.class.show_warnings
end
@warnings.each do |(error, *adapters)|
$stderr.puts "WARNING: " + error if adapters.include?(adapter_name) && self.class.show_warnings
end
if direction != :up
@triggers.each{ |t| t.validate!(:down) } if @triggers
end
if direction != :down
@trigger_group.validate!(:up) if @trigger_group
end
end
|
#warnings ⇒ Object
292
293
294
|
# File 'lib/hair_trigger/builder.rb', line 292
def warnings
(@triggers || []).map(&:warnings).inject(@warnings, &:+)
end
|
#where(where) ⇒ Object
72
73
74
|
# File 'lib/hair_trigger/builder.rb', line 72
def where(where)
options[:where] = where
end
|