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 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