sequel-attribute_callbacks 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +30 -4
- data/lib/sequel-attribute_callbacks/version.rb +1 -1
- data/lib/sequel/plugins/attribute_callbacks.rb +7 -7
- data/lib/sequel/plugins/attribute_callbacks/rich_data_fixes.rb +39 -0
- data/spec/callbacks_spec.rb +87 -22
- metadata +3 -3
- data/lib/sequel/plugins/attribute_callbacks/pg_array_fixes.rb +0 -38
data/README.md
CHANGED
@@ -5,8 +5,8 @@ specific model attribute changes. The hooks are defined with conventionally
|
|
5
5
|
named instance methods for maximum DRYness.
|
6
6
|
|
7
7
|
There's special support for callbacks involving array fields (as in Postgres
|
8
|
-
array types with :pg_array extension), so that they can
|
9
|
-
associations, with add and remove callbacks.
|
8
|
+
array types with :pg_array extension) and hashes (HStore), so that they can
|
9
|
+
be used similarly to associations, with add and remove callbacks.
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
|
@@ -39,7 +39,7 @@ class Person < Sequel::Model
|
|
39
39
|
end
|
40
40
|
```
|
41
41
|
|
42
|
-
Special support for arrays (with pg_array
|
42
|
+
Special support for arrays (with pg_array extension):
|
43
43
|
|
44
44
|
```ruby
|
45
45
|
# widgets table has (colors text[]) column
|
@@ -47,7 +47,7 @@ class Widget < Sequel::Model
|
|
47
47
|
plugin :attribute_callbacks
|
48
48
|
|
49
49
|
def before_colors_add color
|
50
|
-
return false unless Paint.
|
50
|
+
return false unless Paint.color_available? color
|
51
51
|
end
|
52
52
|
|
53
53
|
def after_colors_add color
|
@@ -65,6 +65,32 @@ class Widget < Sequel::Model
|
|
65
65
|
end
|
66
66
|
```
|
67
67
|
|
68
|
+
Special support for hashes (with pg_hstore extension):
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# robots table has (parts hstore) column
|
72
|
+
class Robot < Sequel::Model
|
73
|
+
plugin :attribute_callbacks
|
74
|
+
|
75
|
+
def before_parts_add place, part
|
76
|
+
return false unless Part[part].fits_in? place
|
77
|
+
end
|
78
|
+
|
79
|
+
def after_parts_add place, part
|
80
|
+
Part.order part
|
81
|
+
end
|
82
|
+
|
83
|
+
def before_parts_remove place, part
|
84
|
+
# if you want a different skeleton go make a new robot
|
85
|
+
return true unless place == 'skeleton'
|
86
|
+
end
|
87
|
+
|
88
|
+
def after_parts_remove place, part
|
89
|
+
Part.reduce_consumption part
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
68
94
|
## Contributing
|
69
95
|
|
70
96
|
1. Fork it
|
@@ -5,9 +5,9 @@ module Sequel::Plugins
|
|
5
5
|
def self.apply model
|
6
6
|
model.plugin :dirty
|
7
7
|
|
8
|
-
if defined? ::Sequel::Postgres::PGArray
|
9
|
-
require 'sequel/plugins/attribute_callbacks/
|
10
|
-
model.
|
8
|
+
if defined? ::Sequel::Postgres::PGArray || defined? ::Sequel::Postgres::HStore
|
9
|
+
require 'sequel/plugins/attribute_callbacks/rich_data_fixes'
|
10
|
+
model.plugin RichDataCloner
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -74,8 +74,8 @@ module Sequel::Plugins
|
|
74
74
|
before = before.to_a
|
75
75
|
after = after.to_a
|
76
76
|
|
77
|
-
return false unless (after - before).all? {|x| send add_hook, x} if respond_to? add_hook
|
78
|
-
return false unless (before - after).all? {|x| send rm_hook, x} if respond_to? rm_hook
|
77
|
+
return false unless (after - before).all? {|x| send add_hook, *x} if respond_to? add_hook
|
78
|
+
return false unless (before - after).all? {|x| send rm_hook, *x} if respond_to? rm_hook
|
79
79
|
return true
|
80
80
|
end
|
81
81
|
|
@@ -85,8 +85,8 @@ module Sequel::Plugins
|
|
85
85
|
before = before.to_a
|
86
86
|
after = after.to_a
|
87
87
|
|
88
|
-
(after - before).each {|x| send add_hook, x} if respond_to? add_hook
|
89
|
-
(before - after).each {|x| send rm_hook, x} if respond_to? rm_hook
|
88
|
+
(after - before).each {|x| send add_hook, *x} if respond_to? add_hook
|
89
|
+
(before - after).each {|x| send rm_hook, *x} if respond_to? rm_hook
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Sequel::Plugins::AttributeCallbacks
|
2
|
+
module DelegatorDeepClone
|
3
|
+
# Delegator clone method doesn't clone the delegated to object
|
4
|
+
# which makes it impossible for the Dirty plugin track changes
|
5
|
+
def clone
|
6
|
+
c = super
|
7
|
+
c.__setobj__ __getobj__.clone
|
8
|
+
c
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module RichDataCloner
|
13
|
+
module InstanceMethods
|
14
|
+
def after_initialize
|
15
|
+
super
|
16
|
+
clone_rich_attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
def after_save
|
20
|
+
super
|
21
|
+
clone_rich_attributes
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# those are often going to be modified in place
|
27
|
+
def clone_rich_attributes
|
28
|
+
values.each do |name, value|
|
29
|
+
if value.kind_of?(Sequel::Postgres::PGArray) || value.kind_of?(Sequel::Postgres::HStore)
|
30
|
+
initial_values[name] = value.clone
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Sequel::Postgres::PGArray.send :include, Sequel::Plugins::AttributeCallbacks::DelegatorDeepClone if defined? Sequel::Postgres::PGArray
|
39
|
+
Sequel::Postgres::HStore.send :include, Sequel::Plugins::AttributeCallbacks::DelegatorDeepClone if defined? Sequel::Postgres::HStore
|
data/spec/callbacks_spec.rb
CHANGED
@@ -4,16 +4,19 @@ describe 'attribute_callbacks plugin' do
|
|
4
4
|
include_context 'database'
|
5
5
|
|
6
6
|
before :all do
|
7
|
+
db.execute "CREATE EXTENSION IF NOT EXISTS hstore"
|
7
8
|
db.create_table :widgets do
|
8
9
|
primary_key :id
|
9
10
|
String :name
|
10
11
|
column :colors, 'text[]'
|
12
|
+
column :store, :hstore
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
16
|
before do
|
15
17
|
db.execute "TRUNCATE TABLE widgets"
|
16
18
|
db.extension:pg_array
|
19
|
+
db.extension:pg_hstore
|
17
20
|
end
|
18
21
|
|
19
22
|
let(:model) { Sequel::Model(:widgets) }
|
@@ -77,31 +80,60 @@ describe 'attribute_callbacks plugin' do
|
|
77
80
|
end
|
78
81
|
|
79
82
|
describe 'before_<attribute>_add callbacks' do
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
83
|
+
context "with an array" do
|
84
|
+
it "are called when an instance is being modified" do
|
85
|
+
i = model.create colors: ['red']
|
86
|
+
i.should_receive(:before_colors_add).with('blue').and_return true
|
87
|
+
i.colors += ['blue']
|
88
|
+
i.save.should be
|
89
|
+
model.first.colors.should == ['red', 'blue']
|
90
|
+
end
|
91
|
+
|
92
|
+
it "work with in place modification" do
|
93
|
+
i = model.create colors: ['red']
|
94
|
+
i.should_receive(:before_colors_add).with('blue').and_return true
|
95
|
+
i.will_change_column :colors
|
96
|
+
i.colors << 'blue'
|
97
|
+
i.save.should be
|
98
|
+
model.first.colors.should == ['red', 'blue']
|
99
|
+
end
|
100
|
+
|
101
|
+
it "work with in place modification without will_change_column" do
|
102
|
+
i = model.create colors: ['red']
|
103
|
+
i.should_receive(:before_colors_add).with('blue').and_return true
|
104
|
+
i.colors << 'blue'
|
105
|
+
i.save.should be
|
106
|
+
model.first.colors.should == ['red', 'blue']
|
107
|
+
end
|
95
108
|
end
|
96
109
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
110
|
+
context "with an hstore" do
|
111
|
+
it "are called when an instance is being modified" do
|
112
|
+
i = model.create store: {a: 5}
|
113
|
+
i.should_receive(:before_store_add).with('b', '6').and_return true
|
114
|
+
i.store = i.store.merge b: 6
|
115
|
+
i.save.should be
|
116
|
+
model.first.store.should == {'a' => '5', 'b' => '6'}
|
117
|
+
end
|
118
|
+
|
119
|
+
it "work with in place modification" do
|
120
|
+
i = model.create store: {a: 5}
|
121
|
+
i.should_receive(:before_store_add).with('b', '6').and_return true
|
122
|
+
i.will_change_column :store
|
123
|
+
i.store[:b] = 6
|
124
|
+
i.save.should be
|
125
|
+
model.first.store.should == {'a' => '5', 'b' => '6'}
|
126
|
+
end
|
127
|
+
|
128
|
+
it "work with in place modification without will_change_column" do
|
129
|
+
i = model.create store: {a: 5}
|
130
|
+
i.should_receive(:before_store_add).with('b', '6').and_return true
|
131
|
+
i.store[:b] = 6
|
132
|
+
i.save.should be
|
133
|
+
model.first.store.should == {'a' => '5', 'b' => '6'}
|
134
|
+
end
|
103
135
|
end
|
104
|
-
|
136
|
+
|
105
137
|
it "are called when an instance is being created" do
|
106
138
|
model.any_instance.should_receive(:before_colors_add).with('red').and_return true
|
107
139
|
model.any_instance.should_receive(:before_colors_add).with('blue').and_return true
|
@@ -119,6 +151,14 @@ describe 'attribute_callbacks plugin' do
|
|
119
151
|
end
|
120
152
|
|
121
153
|
describe 'before_<attribute>_remove callbacks' do
|
154
|
+
it "work with hstore" do
|
155
|
+
i = model.create store: {a: 5}
|
156
|
+
i.should_receive(:before_store_remove).with('a', '5').and_return true
|
157
|
+
i.store = {}
|
158
|
+
i.save.should be
|
159
|
+
model.first.store.should == {}
|
160
|
+
end
|
161
|
+
|
122
162
|
it "are called when an instance is being modified" do
|
123
163
|
i = model.create colors: ['red']
|
124
164
|
i.should_receive(:before_colors_remove).with('red').and_return true
|
@@ -138,6 +178,14 @@ describe 'attribute_callbacks plugin' do
|
|
138
178
|
end
|
139
179
|
|
140
180
|
describe 'after_<attribute>_add callbacks' do
|
181
|
+
it "work with hstore" do
|
182
|
+
i = model.create store: {a: 5}
|
183
|
+
i.should_receive(:after_store_add).with('b', '6').and_return true
|
184
|
+
i.store = i.store.merge b: 6
|
185
|
+
i.save.should be
|
186
|
+
model.first.store.should == {'a' => '5', 'b' => '6'}
|
187
|
+
end
|
188
|
+
|
141
189
|
it "are called when an instance is being modified" do
|
142
190
|
i = model.create colors: ['red']
|
143
191
|
i.should_receive(:after_colors_add).with('blue')
|
@@ -163,6 +211,14 @@ describe 'attribute_callbacks plugin' do
|
|
163
211
|
end
|
164
212
|
|
165
213
|
describe 'after_<attribute>_remove callbacks' do
|
214
|
+
it "work with hstore" do
|
215
|
+
i = model.create store: {a: 5}
|
216
|
+
i.should_receive(:after_store_remove).with('a', '5').and_return true
|
217
|
+
i.store = {}
|
218
|
+
i.save.should be
|
219
|
+
model.first.store.should == {}
|
220
|
+
end
|
221
|
+
|
166
222
|
it "are called when an instance is being modified" do
|
167
223
|
i = model.create colors: ['red']
|
168
224
|
i.should_receive(:after_colors_remove).with('red')
|
@@ -180,4 +236,13 @@ describe 'attribute_callbacks plugin' do
|
|
180
236
|
model.first.colors.should == ['red']
|
181
237
|
end
|
182
238
|
end
|
239
|
+
|
240
|
+
it "reports store changes as remove then add" do
|
241
|
+
i = model.create store: {a: 5}
|
242
|
+
i.should_receive(:before_store_remove).with('a', '5').and_return true
|
243
|
+
i.should_receive(:before_store_add).with('a', '6').and_return true
|
244
|
+
i.store[:a] = 6
|
245
|
+
i.save.should be
|
246
|
+
model.first.store.should == {'a' => '6'}
|
247
|
+
end
|
183
248
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel-attribute_callbacks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sequel
|
@@ -91,7 +91,7 @@ files:
|
|
91
91
|
- lib/sequel-attribute_callbacks.rb
|
92
92
|
- lib/sequel-attribute_callbacks/version.rb
|
93
93
|
- lib/sequel/plugins/attribute_callbacks.rb
|
94
|
-
- lib/sequel/plugins/attribute_callbacks/
|
94
|
+
- lib/sequel/plugins/attribute_callbacks/rich_data_fixes.rb
|
95
95
|
- sequel-attribute_callbacks.gemspec
|
96
96
|
- spec/callbacks_spec.rb
|
97
97
|
- spec/spec_helper.rb
|
@@ -1,38 +0,0 @@
|
|
1
|
-
module Sequel::Plugins::AttributeCallbacks
|
2
|
-
module PgArrayFixes
|
3
|
-
module PgArray
|
4
|
-
# Delegator clone method doesn't clone the delegated to object
|
5
|
-
# which makes it impossible for the Dirty plugin track changes
|
6
|
-
def clone
|
7
|
-
c = super
|
8
|
-
c.__setobj__ __getobj__.clone
|
9
|
-
c
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
module AttributeCallbacks
|
14
|
-
def after_initialize
|
15
|
-
super
|
16
|
-
clone_array_attributes
|
17
|
-
end
|
18
|
-
|
19
|
-
def after_save
|
20
|
-
super
|
21
|
-
clone_array_attributes
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
# arrays are probably going to be often modified in place
|
27
|
-
def clone_array_attributes
|
28
|
-
values.each do |name, value|
|
29
|
-
if value.kind_of? Sequel::Postgres::PGArray
|
30
|
-
initial_values[name] = value.clone
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
Sequel::Postgres::PGArray.send :include, Sequel::Plugins::AttributeCallbacks::PgArrayFixes::PgArray if defined? Sequel::Postgres::PGArray
|