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 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 be used similarly to
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 plugin):
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.color_vailable? color
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
@@ -1,5 +1,5 @@
1
1
  module Sequel
2
2
  module AttributeCallbacks
3
- VERSION = "0.0.1"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -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/pg_array_fixes'
10
- model.include PgArrayFixes::AttributeCallbacks
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
@@ -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
- it "are called when an instance is being modified" do
81
- i = model.create colors: ['red']
82
- i.should_receive(:before_colors_add).with('blue').and_return true
83
- i.colors += ['blue']
84
- i.save.should be
85
- model.first.colors.should == ['red', 'blue']
86
- end
87
-
88
- it "work with in place modification" do
89
- i = model.create colors: ['red']
90
- i.should_receive(:before_colors_add).with('blue').and_return true
91
- i.will_change_column :colors
92
- i.colors << 'blue'
93
- i.save.should be
94
- model.first.colors.should == ['red', 'blue']
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
- it "work with in place modification without will_change_column" do
98
- i = model.create colors: ['red']
99
- i.should_receive(:before_colors_add).with('blue').and_return true
100
- i.colors << 'blue'
101
- i.save.should be
102
- model.first.colors.should == ['red', 'blue']
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.1
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 00:00:00.000000000 Z
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/pg_array_fixes.rb
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