sequel-attribute_callbacks 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sequel-attribute_callbacks.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Rafał Rzepecki
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,74 @@
1
+ # Sequel::AttributeCallbacks
2
+
3
+ This plugin for Sequel::Record allows to easily hook in callbacks watching
4
+ specific model attribute changes. The hooks are defined with conventionally
5
+ named instance methods for maximum DRYness.
6
+
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.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'sequel-attribute_callbacks'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install sequel-attribute_callbacks
24
+
25
+ ## Synopsis
26
+
27
+ ```ruby
28
+ # people table has (name text) column
29
+ class Person < Sequel::Model
30
+ plugin :attribute_callbacks
31
+
32
+ def before_name_change old, new_name
33
+ return true unless Dictionary.is_offensive? new_name
34
+ end
35
+
36
+ def after_name_change old, new
37
+ NameChangeRecord.create self.id, old, new
38
+ end
39
+ end
40
+ ```
41
+
42
+ Special support for arrays (with pg_array plugin):
43
+
44
+ ```ruby
45
+ # widgets table has (colors text[]) column
46
+ class Widget < Sequel::Model
47
+ plugin :attribute_callbacks
48
+
49
+ def before_colors_add color
50
+ return false unless Paint.color_vailable? color
51
+ end
52
+
53
+ def after_colors_add color
54
+ Paint.order color
55
+ end
56
+
57
+ def before_colors_remove color
58
+ # this is our company color, we need it!
59
+ return true unless color == 'fuchsia'
60
+ end
61
+
62
+ def after_colors_remove color
63
+ Paint.reduce_consumption color
64
+ end
65
+ end
66
+ ```
67
+
68
+ ## Contributing
69
+
70
+ 1. Fork it
71
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
72
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
73
+ 4. Push to the branch (`git push origin my-new-feature`)
74
+ 5. Create new Pull Request
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
@@ -0,0 +1,2 @@
1
+ require "sequel-attribute_callbacks/version"
2
+ require 'sequel/plugins/attribute_callbacks'
@@ -0,0 +1,5 @@
1
+ module Sequel
2
+ module AttributeCallbacks
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,93 @@
1
+ require 'sequel'
2
+
3
+ module Sequel::Plugins
4
+ module AttributeCallbacks
5
+ def self.apply model
6
+ model.plugin :dirty
7
+
8
+ if defined? ::Sequel::Postgres::PGArray
9
+ require 'sequel/plugins/attribute_callbacks/pg_array_fixes'
10
+ model.include PgArrayFixes::AttributeCallbacks
11
+ end
12
+ end
13
+
14
+ module InstanceMethods
15
+ def before_update
16
+ (column_changes || []).each do |column, change|
17
+ return false unless call_before_attribute_hook column, change
18
+ end
19
+ super
20
+ end
21
+
22
+ def after_update
23
+ super
24
+ (previous_changes || []).each do |column, change|
25
+ call_after_attribute_hook column, change
26
+ end
27
+ end
28
+
29
+ def before_create
30
+ columns.each do |column|
31
+ value = send column
32
+ return false unless call_before_attribute_hook column, [nil, value] if value
33
+ end
34
+ super
35
+ end
36
+
37
+ def after_create
38
+ super
39
+ columns.each do |column|
40
+ value = send column
41
+ call_after_attribute_hook column, [nil, value] if value
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def call_after_attribute_hook column, change
48
+ method = "after_#{column}_change".to_sym
49
+ send method, *change if respond_to? method
50
+ call_after_array_hooks column, *change if change.all?{|x| x.respond_to? :to_a}
51
+ end
52
+
53
+ def call_before_attribute_hook column, change
54
+ method = "before_#{column}_change".to_sym
55
+
56
+ scalar = if respond_to? method
57
+ send method, *change
58
+ else
59
+ true
60
+ end
61
+
62
+ return false unless scalar
63
+
64
+ if change.all?{|x| x.respond_to? :to_a}
65
+ call_before_array_hooks column, *change
66
+ else
67
+ true
68
+ end
69
+ end
70
+
71
+ def call_before_array_hooks column, before, after
72
+ add_hook = "before_#{column}_add".to_sym
73
+ rm_hook = "before_#{column}_remove".to_sym
74
+ before = before.to_a
75
+ after = after.to_a
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
79
+ return true
80
+ end
81
+
82
+ def call_after_array_hooks column, before, after
83
+ add_hook = "after_#{column}_add".to_sym
84
+ rm_hook = "after_#{column}_remove".to_sym
85
+ before = before.to_a
86
+ after = after.to_a
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
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,38 @@
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
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sequel-attribute_callbacks/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "sequel-attribute_callbacks"
8
+ gem.version = Sequel::AttributeCallbacks::VERSION
9
+ gem.authors = ["Rafał Rzepecki"]
10
+ gem.email = ["divided.mind@gmail.com"]
11
+ gem.description = %q{Model plugin making it easy to define callbacks on modification of specific attributes in the database}
12
+ gem.summary = %q{Attribute modification callbacks}
13
+ gem.homepage = "https://github.com/dividedmind/sequel-attribute_callbacks"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency 'sequel', '~>3.44'
21
+
22
+ gem.add_development_dependency 'rake', '~>10.0'
23
+ gem.add_development_dependency 'rspec', '~>2.12'
24
+ gem.add_development_dependency 'pg'
25
+ end
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'attribute_callbacks plugin' do
4
+ include_context 'database'
5
+
6
+ before :all do
7
+ db.create_table :widgets do
8
+ primary_key :id
9
+ String :name
10
+ column :colors, 'text[]'
11
+ end
12
+ end
13
+
14
+ before do
15
+ db.execute "TRUNCATE TABLE widgets"
16
+ db.extension:pg_array
17
+ end
18
+
19
+ let(:model) { Sequel::Model(:widgets) }
20
+ subject { model }
21
+
22
+ before {
23
+ model.instance_eval {
24
+ plugin :attribute_callbacks
25
+ }
26
+ }
27
+
28
+ it "doesn't interfere with record creation" do
29
+ model.create
30
+ end
31
+
32
+ describe 'after_<attribute>_change callbacks' do
33
+ it "are called when an instance is being modified" do
34
+ i = model.create
35
+ i.should_receive(:after_name_change).with(nil, 'foo')
36
+ i.name = 'foo'
37
+ i.save
38
+ end
39
+
40
+ it "are called when an instance is being created" do
41
+ model.any_instance.should_receive(:after_name_change).with(nil, 'foo')
42
+ i = model.create name: "foo"
43
+ end
44
+
45
+ it "roll back the change if an exception is thrown" do
46
+ i = model.create name: "foo"
47
+ i.should_receive(:after_name_change).with("foo", "bar").and_raise Exception
48
+ i.name = 'bar'
49
+ expect { i.save }.to raise_error
50
+
51
+ model.first.name.should == "foo"
52
+ end
53
+ end
54
+
55
+ describe 'before_<attribute>_change callbacks' do
56
+ it "are called when an instance is being modified" do
57
+ i = model.create
58
+ i.should_receive(:before_name_change).with(nil, 'foo').and_return true
59
+ i.name = 'foo'
60
+ i.save.should be
61
+ model.first.name.should == "foo"
62
+ end
63
+
64
+ it "are called when an instance is being created" do
65
+ model.any_instance.should_receive(:before_name_change).with(nil, 'foo').and_return true
66
+ i = model.create name: "foo"
67
+ end
68
+
69
+ it "cancel the change if false is returned" do
70
+ i = model.create name: "foo"
71
+ i.should_receive(:before_name_change).with("foo", "bar").and_return false
72
+ i.name = 'bar'
73
+ expect { i.save }.to raise_error(Sequel::HookFailed)
74
+
75
+ model.first.name.should == "foo"
76
+ end
77
+ end
78
+
79
+ 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']
95
+ end
96
+
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']
103
+ end
104
+
105
+ it "are called when an instance is being created" do
106
+ model.any_instance.should_receive(:before_colors_add).with('red').and_return true
107
+ model.any_instance.should_receive(:before_colors_add).with('blue').and_return true
108
+ i = model.create colors: ['red', 'blue']
109
+ end
110
+
111
+ it "cancel the change if false is returned" do
112
+ i = model.create colors: ['red']
113
+ i.should_receive(:before_colors_add).with('blue').and_return false
114
+ i.colors += ['blue']
115
+ expect { i.save }.to raise_error(Sequel::HookFailed)
116
+
117
+ model.first.colors.should == ['red']
118
+ end
119
+ end
120
+
121
+ describe 'before_<attribute>_remove callbacks' do
122
+ it "are called when an instance is being modified" do
123
+ i = model.create colors: ['red']
124
+ i.should_receive(:before_colors_remove).with('red').and_return true
125
+ i.colors -= ['red']
126
+ i.save.should be
127
+ model.first.colors.should == []
128
+ end
129
+
130
+ it "cancel the change if false is returned" do
131
+ i = model.create colors: ['red']
132
+ i.should_receive(:before_colors_remove).with('red').and_return false
133
+ i.colors -= ['red']
134
+ expect { i.save }.to raise_error(Sequel::HookFailed)
135
+
136
+ model.first.colors.should == ['red']
137
+ end
138
+ end
139
+
140
+ describe 'after_<attribute>_add callbacks' do
141
+ it "are called when an instance is being modified" do
142
+ i = model.create colors: ['red']
143
+ i.should_receive(:after_colors_add).with('blue')
144
+ i.colors << 'blue'
145
+ i.save.should be
146
+ model.first.colors.should == ['red', 'blue']
147
+ end
148
+
149
+ it "are called when an instance is being created" do
150
+ model.any_instance.should_receive(:after_colors_add).with('red')
151
+ model.any_instance.should_receive(:after_colors_add).with('blue')
152
+ i = model.create colors: ['red', 'blue']
153
+ end
154
+
155
+ it "cancel the change if an exception is thrown" do
156
+ i = model.create colors: ['red']
157
+ i.should_receive(:after_colors_add).with('blue').and_raise Exception
158
+ i.colors << 'blue'
159
+ expect { i.save }.to raise_error
160
+
161
+ model.first.colors.should == ['red']
162
+ end
163
+ end
164
+
165
+ describe 'after_<attribute>_remove callbacks' do
166
+ it "are called when an instance is being modified" do
167
+ i = model.create colors: ['red']
168
+ i.should_receive(:after_colors_remove).with('red')
169
+ i.colors -= ['red']
170
+ i.save.should be
171
+ model.first.colors.should == []
172
+ end
173
+
174
+ it "cancel the change if an exception is thrown" do
175
+ i = model.create colors: ['red']
176
+ i.should_receive(:after_colors_remove).with('red').and_raise Exception
177
+ i.colors -= ['red']
178
+ expect { i.save }.to raise_error
179
+
180
+ model.first.colors.should == ['red']
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,15 @@
1
+ require 'sequel-attribute_callbacks'
2
+
3
+ shared_context 'database' do
4
+ let(:dburl) { ENV['TEST_DATABASE_URL'] || 'postgres:///sequel-attribute_callbacks_test' }
5
+ let(:db) { Sequel::connect dburl }
6
+
7
+ let(:clean_database) {
8
+ db.execute """
9
+ DROP SCHEMA public CASCADE;
10
+ CREATE SCHEMA public;
11
+ """
12
+ }
13
+
14
+ before(:all) { clean_database }
15
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel-attribute_callbacks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rafał Rzepecki
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sequel
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.44'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.44'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '10.0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '10.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.12'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.12'
62
+ - !ruby/object:Gem::Dependency
63
+ name: pg
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Model plugin making it easy to define callbacks on modification of specific
79
+ attributes in the database
80
+ email:
81
+ - divided.mind@gmail.com
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - Gemfile
88
+ - LICENSE.txt
89
+ - README.md
90
+ - Rakefile
91
+ - lib/sequel-attribute_callbacks.rb
92
+ - lib/sequel-attribute_callbacks/version.rb
93
+ - lib/sequel/plugins/attribute_callbacks.rb
94
+ - lib/sequel/plugins/attribute_callbacks/pg_array_fixes.rb
95
+ - sequel-attribute_callbacks.gemspec
96
+ - spec/callbacks_spec.rb
97
+ - spec/spec_helper.rb
98
+ homepage: https://github.com/dividedmind/sequel-attribute_callbacks
99
+ licenses: []
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 1.8.24
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: Attribute modification callbacks
122
+ test_files:
123
+ - spec/callbacks_spec.rb
124
+ - spec/spec_helper.rb
125
+ has_rdoc: