sequel-attribute_callbacks 0.0.1 → 0.1.0
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.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
|