do_not_want 0.0.1
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/.gemtest +0 -0
- data/CHANGELOG.md +5 -0
- data/Manifest.txt +8 -0
- data/README.md +47 -0
- data/Rakefile +22 -0
- data/lib/do_not_want.rb +80 -0
- data/spec/do_not_want_spec.rb +46 -0
- data/spec/gems/fake_gem.rb +4 -0
- data/spec/rails_integration_spec.rb +53 -0
- metadata +111 -0
data/.gemtest
ADDED
File without changes
|
data/CHANGELOG.md
ADDED
data/Manifest.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Do Not Want
|
2
|
+
|
3
|
+
* http://github.com/garybernhardt/do_not_want
|
4
|
+
|
5
|
+
## DESCRIPTION:
|
6
|
+
|
7
|
+
Several methods in ActiveRecord skip validations, callbacks, or both. In my extremely humble but also extremely correct opinion, it's too easy to accidentally use these.
|
8
|
+
|
9
|
+
Do Not Want kills those methods dead so you won't cut yourself on them:
|
10
|
+
|
11
|
+
>> User.new.update_attribute(:foo, 5)
|
12
|
+
DoNotWant::NotSafe: User#update_attribute isn't safe because it skips validation
|
13
|
+
|
14
|
+
## Why Do It Do It?
|
15
|
+
|
16
|
+
In my experience, even experienced Rails developers don't know which ActiveRecord methods skip validations and callbacks. Quick: which of `decrement`, `decrement!`, and `decrement_counter` skip which? (Hint: they're all different.)
|
17
|
+
|
18
|
+
## How Do It Do It?
|
19
|
+
|
20
|
+
It `define_method`s them away.
|
21
|
+
|
22
|
+
But! Calls to the unsafe methods are allowed from within gems. This keeps Rails from breaking, and allows third-party code to do as it pleases while keeping your app as jank-free as possible.
|
23
|
+
|
24
|
+
The disabled instance methods are:
|
25
|
+
|
26
|
+
decrement
|
27
|
+
decrement!
|
28
|
+
increment
|
29
|
+
ncrement!
|
30
|
+
toggle
|
31
|
+
toggle!
|
32
|
+
update_attribute
|
33
|
+
|
34
|
+
The disabled class methods are:
|
35
|
+
|
36
|
+
decrement_counter
|
37
|
+
delete
|
38
|
+
delete_all
|
39
|
+
find_by_sql
|
40
|
+
increment_counter
|
41
|
+
update_all
|
42
|
+
update_counters
|
43
|
+
|
44
|
+
The particular transgressions that these methods make are documented in the source.
|
45
|
+
|
46
|
+
The Rails [ActiveRecord guide](http://guides.rubyonrails.org/active_record_validations_callbacks.html#skipping-validations) contains lists about methods that skip validation and callbacks. That's where this list came from.
|
47
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hoe'
|
3
|
+
|
4
|
+
Hoe.plugin :git, :doofus, :seattlerb
|
5
|
+
|
6
|
+
Hoe.spec 'do_not_want' do
|
7
|
+
developer 'Gary Bernhardt', 'gary.bernhardt@gmail.com'
|
8
|
+
|
9
|
+
### Use markdown for changelog and readme
|
10
|
+
self.history_file = 'CHANGELOG.md'
|
11
|
+
self.readme_file = 'README.md'
|
12
|
+
|
13
|
+
### Test with rspec
|
14
|
+
self.extra_dev_deps << [ 'rspec', '>= 2.1.0' ]
|
15
|
+
self.testlib = :rspec
|
16
|
+
|
17
|
+
### build zip files too
|
18
|
+
self.need_zip = true
|
19
|
+
end
|
20
|
+
|
21
|
+
# add pointer so gem test works. bleh.
|
22
|
+
task :test => [:spec]
|
data/lib/do_not_want.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module DoNotWant
|
2
|
+
VERSION = "0.0.1"
|
3
|
+
|
4
|
+
# Bad methods and their reasons
|
5
|
+
|
6
|
+
BAD_INSTANCE_METHODS = {
|
7
|
+
:decrement => ["callbacks"],
|
8
|
+
:decrement! => ["validation"],
|
9
|
+
:increment => ["callbacks"],
|
10
|
+
:increment! => ["validation"],
|
11
|
+
:toggle => ["callbacks"],
|
12
|
+
:toggle! => ["validation"],
|
13
|
+
:update_attribute => ["validation"],
|
14
|
+
}
|
15
|
+
BAD_INSTANCE_METHOD_NAMES = BAD_INSTANCE_METHODS.keys
|
16
|
+
|
17
|
+
BAD_CLASS_METHODS = {
|
18
|
+
:decrement_counter => ["validation", "callbacks"],
|
19
|
+
:delete => ["callbacks"],
|
20
|
+
:delete_all => ["callbacks"],
|
21
|
+
:find_by_sql => ["callbacks"],
|
22
|
+
:increment_counter => ["validation", "callbacks"],
|
23
|
+
:update_all => ["validation", "callbacks"],
|
24
|
+
:update_counters => ["validation", "callbacks"],
|
25
|
+
}
|
26
|
+
BAD_CLASS_METHOD_NAMES = BAD_CLASS_METHODS.keys
|
27
|
+
|
28
|
+
class NotSafe < Exception
|
29
|
+
def initialize(called_object, called_method, reason)
|
30
|
+
class_name = called_object.class.name
|
31
|
+
method_name = called_method.to_s
|
32
|
+
|
33
|
+
method_description = if called_object.is_a?(Class)
|
34
|
+
"#{called_object.name}.#{method_name}"
|
35
|
+
else
|
36
|
+
"#{class_name}##{method_name}"
|
37
|
+
end
|
38
|
+
|
39
|
+
super "#{method_description} isn't safe because %s" % [
|
40
|
+
reason
|
41
|
+
]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.should_validate_for_caller(caller)
|
46
|
+
/\/gems\//.match(caller[0])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Object
|
51
|
+
def self.do_not_want!(method_name, reason)
|
52
|
+
original_method_name = ('do_not_want_original_' + method_name.to_s).to_sym
|
53
|
+
self.send :alias_method, original_method_name, method_name
|
54
|
+
|
55
|
+
self.send :define_method, method_name do |*args|
|
56
|
+
original_method_name = ('do_not_want_original_' + method_name.to_s).to_sym
|
57
|
+
use_real_method = DoNotWant.should_validate_for_caller(caller)
|
58
|
+
if use_real_method
|
59
|
+
return self.send original_method_name, *args
|
60
|
+
end
|
61
|
+
raise DoNotWant::NotSafe.new(self, method_name, reason)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module ActiveRecord
|
67
|
+
class Base
|
68
|
+
|
69
|
+
DoNotWant::BAD_INSTANCE_METHODS.each do |method_name, reasons|
|
70
|
+
do_not_want!(method_name, "it skips #{reasons.join(' and ')}")
|
71
|
+
end
|
72
|
+
|
73
|
+
class << self
|
74
|
+
DoNotWant::BAD_CLASS_METHODS.each do |method_name, reasons|
|
75
|
+
do_not_want!(method_name, "it skips #{reasons.join(' and ')}")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'do_not_want'
|
3
|
+
require 'gems/fake_gem'
|
4
|
+
|
5
|
+
class Walrus
|
6
|
+
def be_killed_by!(killer, reason)
|
7
|
+
die!
|
8
|
+
"killed by #{killer} because #{reason}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def die!
|
12
|
+
end
|
13
|
+
|
14
|
+
do_not_want! :be_killed_by!, 'because dying sucks'
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'do not want' do
|
18
|
+
let(:walrus) { Walrus.new }
|
19
|
+
|
20
|
+
context "method calls" do
|
21
|
+
it "raises an error for unwanted method calls" do
|
22
|
+
walrus.should_not_receive(:die!)
|
23
|
+
expect do
|
24
|
+
walrus.be_killed_by!
|
25
|
+
end.to raise_error(DoNotWant::NotSafe)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "lets other methods through" do
|
29
|
+
walrus.class.should == Walrus
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "caller filtering" do
|
34
|
+
it "ignores calls from gems" do
|
35
|
+
walrus.should_receive(:die!)
|
36
|
+
expect do
|
37
|
+
kill_walrus_from_gem(walrus)
|
38
|
+
end.not_to raise_error
|
39
|
+
end
|
40
|
+
|
41
|
+
it "passes arguments" do
|
42
|
+
kill_walrus_from_gem(walrus).should == 'killed by kitty because kitty is angry'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'do_not_want'
|
3
|
+
|
4
|
+
class Cheese < ActiveRecord::Base
|
5
|
+
end
|
6
|
+
|
7
|
+
describe 'rails integration' do
|
8
|
+
before do
|
9
|
+
ActiveRecord::Base.establish_connection(
|
10
|
+
:adapter => "sqlite3",
|
11
|
+
:database => ":memory:")
|
12
|
+
|
13
|
+
ActiveRecord::Base.connection.create_table(:cheeses) do |t|
|
14
|
+
t.string :name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:cheese) { Cheese.create! }
|
19
|
+
it 'rejects unsafe instance methods' do
|
20
|
+
DoNotWant::BAD_INSTANCE_METHOD_NAMES.each do |method_name|
|
21
|
+
expect do
|
22
|
+
cheese.send method_name
|
23
|
+
end.to raise_error DoNotWant::NotSafe
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'allows safe instance methods' do
|
28
|
+
cheese.reload.should == cheese
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'rejects unsafe class methods' do
|
32
|
+
DoNotWant::BAD_CLASS_METHOD_NAMES.each do |method_name|
|
33
|
+
expect { Cheese.send method_name }.to raise_error DoNotWant::NotSafe
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'allows safe class methods' do
|
38
|
+
Cheese.columns.count.should == 2
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'gives reasons' do
|
42
|
+
expect { cheese.decrement }.to raise_error(
|
43
|
+
DoNotWant::NotSafe,
|
44
|
+
"Cheese#decrement isn't safe because it skips callbacks")
|
45
|
+
expect { cheese.decrement! }.to raise_error(
|
46
|
+
DoNotWant::NotSafe,
|
47
|
+
"Cheese#decrement! isn't safe because it skips validation")
|
48
|
+
expect { Cheese.update_all }.to raise_error(
|
49
|
+
DoNotWant::NotSafe,
|
50
|
+
"Cheese.update_all isn't safe because it skips validation and callbacks")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: do_not_want
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Gary Bernhardt
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-20 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 11
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
version: 2.1.0
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: hoe
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 27
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 12
|
48
|
+
version: "2.12"
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
description: |-
|
52
|
+
Several methods in ActiveRecord skip validations, callbacks, or both. In my extremely humble but also extremely correct opinion, it's too easy to accidentally use these.
|
53
|
+
|
54
|
+
Do Not Want kills those methods dead so you won't cut yourself on them:
|
55
|
+
|
56
|
+
>> User.new.update_attribute(:foo, 5)
|
57
|
+
DoNotWant::NotSafe: User#update_attribute isn't safe because it skips validation
|
58
|
+
email:
|
59
|
+
- gary.bernhardt@gmail.com
|
60
|
+
executables: []
|
61
|
+
|
62
|
+
extensions: []
|
63
|
+
|
64
|
+
extra_rdoc_files:
|
65
|
+
- Manifest.txt
|
66
|
+
files:
|
67
|
+
- CHANGELOG.md
|
68
|
+
- Manifest.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- lib/do_not_want.rb
|
72
|
+
- spec/do_not_want_spec.rb
|
73
|
+
- spec/gems/fake_gem.rb
|
74
|
+
- spec/rails_integration_spec.rb
|
75
|
+
- .gemtest
|
76
|
+
homepage: http://github.com/garybernhardt/do_not_want
|
77
|
+
licenses: []
|
78
|
+
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options:
|
81
|
+
- --main
|
82
|
+
- README.md
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 3
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
hash: 3
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
requirements: []
|
104
|
+
|
105
|
+
rubyforge_project: do_not_want
|
106
|
+
rubygems_version: 1.8.8
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: Several methods in ActiveRecord skip validations, callbacks, or both
|
110
|
+
test_files: []
|
111
|
+
|