hairtrigger 0.1.1 → 0.1.2
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.
- data/README.rdoc +2 -2
- data/VERSION +1 -1
- data/lib/hair_trigger/builder.rb +11 -9
- data/spec/builder_spec.rb +20 -5
- metadata +4 -4
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
|
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
|
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
|
+
0.1.2
|
data/lib/hair_trigger/builder.rb
CHANGED
@@ -68,9 +68,14 @@ module HairTrigger
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def security(user)
|
71
|
-
|
72
|
-
raise_or_warn "sqlite doesn't support trigger security
|
73
|
-
raise_or_warn "postgresql doesn't support arbitrary users for security
|
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#{
|
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
|
8
|
-
def initialize(type
|
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"
|
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"
|
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:
|
4
|
+
hash: 31
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.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-
|
18
|
+
date: 2011-03-23 00:00:00 -06:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|