hairtrigger 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -55,7 +55,7 @@ include:
55
55
  * before(*events): Shorthand for timing(:before).events(*events).
56
56
  * after(*events): Shorthand for timing(:after).events(*events).
57
57
  * where(conditions): Optional, limits when the trigger will fire.
58
- * security(user): Permissions/role to check when calling trigger, defaults to :invoker. PostgreSQL supports :definer, MySQL supports :definer and arbitrary users (syntax: 'user'@'host').
58
+ * security(user): Permissions/role to check when calling trigger. PostgreSQL supports :invoker (default) and :definer, MySQL supports :definer (default) and arbitrary users (syntax: 'user'@'host').
59
59
  * timing(timing): Required (but may be satisified by before/after). Possible values are :before/:after.
60
60
  * events(*events): Required (but may be satisified by before/after). MySQL/SQLite only support one action.
61
61
  * all: Noop, useful for trigger groups (see below).
@@ -108,7 +108,7 @@ you want to support.
108
108
 
109
109
  * HairTrigger does not check config.active_record.timestamped_migrations, it
110
110
  always assumes it is true. As a workaround, you can rename the migration.
111
- * As is the case with ActiveRecord::Base#update_all or any direct SQL you do,
111
+ * As is the case with ActiveRecord::Base.update_all or any direct SQL you do,
112
112
  be careful to reload updated objects from the database. For example, the
113
113
  following code will display the wrong count since we aren't reloading the
114
114
  account:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
@@ -68,9 +68,14 @@ module HairTrigger
68
68
  end
69
69
 
70
70
  def security(user)
71
- return if user == :invoker # default behavior
72
- raise_or_warn "sqlite doesn't support trigger security clauses", :sqlite
73
- raise_or_warn "postgresql doesn't support arbitrary users for security clauses", :postgresql unless user == :definer
71
+ # sqlite default is n/a, mysql default is :definer, postgres default is :invoker
72
+ raise_or_warn "sqlite doesn't support trigger security", :sqlite
73
+ raise_or_warn "postgresql doesn't support arbitrary users for trigger security", :postgresql unless [:definer, :invoker].include?(user)
74
+ if user == :invoker
75
+ raise_or_warn "mysql doesn't support invoker trigger security", :mysql
76
+ elsif user != :definer && !(user.to_s =~ /\A'[^']+'@'[^']+'\z/) && !(user.to_s.downcase =~ /\Acurrent_user(\(\))?\z/)
77
+ raise_or_warn "mysql trigger security should be :definer, CURRENT_USER, or a valid mysql user (e.g. 'user'@'host')", :mysql
78
+ end
74
79
  options[:security] = user
75
80
  end
76
81
 
@@ -274,24 +279,21 @@ END;
274
279
  end
275
280
 
276
281
  def generate_trigger_postgresql
282
+ security = options[:security] if options[:security] && options[:security] != :invoker
277
283
  <<-SQL
278
284
  CREATE FUNCTION #{prepared_name}()
279
285
  RETURNS TRIGGER AS $$
280
286
  BEGIN
281
287
  #{normalize(prepared_actions, 1).rstrip}
282
288
  END;
283
- $$ LANGUAGE plpgsql#{options[:security] ? " SECURITY #{options[:security].to_s.upcase}" : ""};
289
+ $$ LANGUAGE plpgsql#{security ? " SECURITY #{security.to_s.upcase}" : ""};
284
290
  CREATE TRIGGER #{prepared_name} #{options[:timing]} #{options[:events].join(" OR ")} ON #{options[:table]}
285
291
  FOR EACH #{options[:for_each]}#{prepared_where ? " WHEN (" + prepared_where + ')': ''} EXECUTE PROCEDURE #{prepared_name}();
286
292
  SQL
287
293
  end
288
294
 
289
295
  def generate_trigger_mysql
290
- security = options[:security]
291
- if security == :definer
292
- config = @adapter.instance_variable_get(:@config)
293
- security = "'#{config[:username]}'@'#{config[:host]}'"
294
- end
296
+ security = options[:security] if options[:security] && options[:security] != :definer
295
297
  sql = <<-SQL
296
298
  CREATE #{security ? "DEFINER = #{security} " : ""}TRIGGER #{prepared_name} #{options[:timing]} #{options[:events].first} ON #{options[:table]}
297
299
  FOR EACH #{options[:for_each]}
data/spec/builder_spec.rb CHANGED
@@ -4,10 +4,9 @@ require File.expand_path(File.dirname(__FILE__) + '/../lib/hair_trigger/builder.
4
4
  HairTrigger::Builder.show_warnings = false
5
5
 
6
6
  class MockAdapter
7
- attr_reader :adapter_name, :config
8
- def initialize(type, user = nil, host = nil)
7
+ attr_reader :adapter_name
8
+ def initialize(type)
9
9
  @adapter_name = type
10
- @config = {:username => user, :host => host}
11
10
  end
12
11
  end
13
12
 
@@ -18,14 +17,14 @@ end
18
17
  describe "builder" do
19
18
  context "chaining" do
20
19
  it "should use the last redundant chained call" do
21
- @adapter = MockAdapter.new("mysql", "user", "host")
20
+ @adapter = MockAdapter.new("mysql")
22
21
  builder.where(:foo).where(:bar).options[:where].should be(:bar)
23
22
  end
24
23
  end
25
24
 
26
25
  context "mysql" do
27
26
  before(:each) do
28
- @adapter = MockAdapter.new("mysql", "user", "host")
27
+ @adapter = MockAdapter.new("mysql")
29
28
  end
30
29
 
31
30
  it "should create a single trigger for a group" do
@@ -49,9 +48,18 @@ describe "builder" do
49
48
 
50
49
  it "should accept security" do
51
50
  builder.on(:foos).after(:update).security(:definer){ "FOO" }.generate.
51
+ grep(/DEFINER/).size.should eql(0) # default, so we don't include it
52
+ builder.on(:foos).after(:update).security("CURRENT_USER"){ "FOO" }.generate.
53
+ grep(/DEFINER = CURRENT_USER/).size.should eql(1)
54
+ builder.on(:foos).after(:update).security("'user'@'host'"){ "FOO" }.generate.
52
55
  grep(/DEFINER = 'user'@'host'/).size.should eql(1)
53
56
  end
54
57
 
58
+ it "should reject :invoker security" do
59
+ lambda { builder.on(:foos).after(:update).security(:invoker){ "FOO" } }.
60
+ should raise_error
61
+ end
62
+
55
63
  it "should reject multiple timings" do
56
64
  lambda { builder.on(:foos).after(:update, :delete){ "FOO" } }.
57
65
  should raise_error
@@ -83,10 +91,17 @@ describe "builder" do
83
91
  end
84
92
 
85
93
  it "should accept security" do
94
+ builder.on(:foos).after(:update).security(:invoker){ "FOO" }.generate.
95
+ grep(/SECURITY/).size.should eql(0) # default, so we don't include it
86
96
  builder.on(:foos).after(:update).security(:definer){ "FOO" }.generate.
87
97
  grep(/SECURITY DEFINER/).size.should eql(1)
88
98
  end
89
99
 
100
+ it "should reject arbitrary user security" do
101
+ lambda { builder.on(:foos).after(:update).security("'user'@'host'"){ "FOO" } }.
102
+ should raise_error
103
+ end
104
+
90
105
  it "should accept multiple timings" do
91
106
  builder.on(:foos).after(:update, :delete){ "FOO" }.generate.
92
107
  grep(/UPDATE OR DELETE/).size.should eql(1)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hairtrigger
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 1
10
- version: 0.1.1
9
+ - 2
10
+ version: 0.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jon Jensen
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-22 00:00:00 -06:00
18
+ date: 2011-03-23 00:00:00 -06:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency