Class: HairTrigger::Builder
- Inherits:
-
Object
- Object
- HairTrigger::Builder
- Defined in:
- lib/hair_trigger/builder.rb
Defined Under Namespace
Classes: DeclarationError, GenerationError
Class Attribute Summary collapse
Instance Attribute Summary collapse
-
#options ⇒ Object
Returns the value of attribute options.
-
#prepared_actions ⇒ Object
readonly
after delayed interpolation.
-
#prepared_where ⇒ Object
readonly
after delayed interpolation.
-
#triggers ⇒ Object
readonly
nil unless this is a trigger group.
Class Method Summary collapse
Instance Method Summary collapse
- #<=>(other) ⇒ Object
- #==(other) ⇒ Object
- #after(*events) ⇒ Object
-
#all ⇒ Object
noop, just a way you can pass a block within a trigger group.
- #all_names ⇒ Object
- #all_triggers(include_self = true) ⇒ Object
- #before(*events) ⇒ Object
- #change_clause(column) ⇒ Object
- #components ⇒ Object
- #create_grouped_trigger? ⇒ Boolean
- #declare(declarations) ⇒ Object
- #drop_triggers ⇒ Object
- #eql?(other) ⇒ Boolean
- #errors ⇒ Object
- #events(*events) ⇒ Object
- #for_each(for_each) ⇒ Object
- #generate(validate = true) ⇒ Object
- #hash ⇒ Object
-
#initialize(name = nil, options = {}) ⇒ Builder
constructor
A new instance of Builder.
- #initialize_copy(other) ⇒ Object
- #name(name) ⇒ Object
- #nowrap(flag = true) ⇒ Object
- #of(*columns) ⇒ Object
- #on(table) ⇒ Object
- #prepare! ⇒ Object
- #prepare_where! ⇒ Object
- #prepared_name ⇒ Object
- #raw_actions ⇒ Object
- #security(user) ⇒ Object
- #timing(timing) ⇒ Object
- #to_ruby(indent = '', always_generated = true) ⇒ Object
- #validate!(direction = :down) ⇒ Object
- #warnings ⇒ Object
- #where(where) ⇒ Object
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, = {}) @adapter = [:adapter] @compatibility = .delete(:compatibility) || self.class.compatibility @options = {} @chained_calls = [] @errors = [] @warnings = [] set_name(name) if name {:timing => :after, :for_each => :row}.update().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 end |
#prepared_actions ⇒ Object (readonly)
after delayed interpolation
10 11 12 |
# File 'lib/hair_trigger/builder.rb', line 10 def prepared_actions @prepared_actions end |
#prepared_where ⇒ Object (readonly)
after delayed interpolation
10 11 12 |
# File 'lib/hair_trigger/builder.rb', line 10 def prepared_where @prepared_where end |
#triggers ⇒ Object (readonly)
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, &block) @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? ? block : nil)) end def set_#{method}(*args, &block) 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? ? block : nil)) else #{method}_orig(*args, &block) maybe_execute(&block) 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 # initial releases else 1 # postgres RETURN bugfix # TODO: add more as we implement things that change the generated # triggers (e.g. chained call merging) 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 || adapter_name == :trilogy end |
#declare(declarations) ⇒ Object
85 86 87 |
# File 'lib/hair_trigger/builder.rb', line 85 def declare(declarations) [: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 => [: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) [: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) [: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 [:table] if [: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 ![:events] || [:events].empty? raise GenerationError, "need to specify the timing (:before/:after)" unless [:timing] [generate_drop_trigger] + [case adapter_name when :sqlite generate_trigger_sqlite when :mysql, :trilogy 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) # this will be inferred (or set further down the line) @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 [:name] = name.to_s end |
#nowrap(flag = true) ⇒ Object
76 77 78 |
# File 'lib/hair_trigger/builder.rb', line 76 def nowrap(flag = true) [: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? [: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 [:table] [: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 # ensure (component) trigger names are all cached 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 = [:where] = interpolate([:where]) if [:where] parts << [:of].map{ |col| change_clause(col) }.join(" OR ") if [: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 ||= [: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 # sqlite default is n/a, mysql default is :definer, postgres default is :invoker @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 [: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) [: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 [:drop] str = "#{indent}drop_trigger(#{prepared_name.inspect}, #{[:table].inspect}" str << ", :generated => true" if always_generated || [: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 || [: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) [:where] = where end |