foreign_key_validation 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +3 -6
- data/lib/foreign_key_validation/model_extension.rb +15 -11
- data/lib/foreign_key_validation/version.rb +1 -1
- data/spec/models/model_spec.rb +43 -18
- data/spec/support/load_models.rb +22 -0
- data/spec/support/reset_models.rb +8 -5
- data/spec/support/schema.rb +10 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NjBhMDg4MzJhYjgzMjlkMDJkZDBiODI5ZGM1ZjE5MWQ2NmI1YzIxMw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZTBiZTExNmE4NjM4MGRmM2QwYjI3MmExZDBlMmU3NjY4ZDlkYTIzMw==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZDM4OGQ0ZDRhYjNhOTFhNzIzMTM4YzUxOWI1ZWJhZGNjYjNhNDgzNDEyMWVl
|
10
|
+
YzA1ODQwYzRjYjY5MGY2MTBlMTMxOGU5MDE2YjRlMjgyNmMyZWFkNGJlODJk
|
11
|
+
NjFiYjAyZjIyYTg5MWUzMDhmMWU5MjNlNGIzNjMxZGE5MDIxOWQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YTYzOTRlODQ1ZjgwYWFlNTIzYmRmNDRmNTVjMDJhMTViNzVmNTc3ZmNlYThl
|
14
|
+
NWE0Yjc3NDA4OTgyZWUxN2M3ZTJlZDQ1MTYwMmFjZDY2MzM5ZDllMjM3ZTNk
|
15
|
+
YzI1NWNmZTI4ZjI1NmNiMDMzOWU0Y2U1MjRjZWVlY2Q1MDMyYTA=
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# foreign_key_validation
|
2
2
|
|
3
|
-
Protect your models by specifying a collection of
|
3
|
+
Protect your models by specifying a collection of relations that should be tested for consistency with the `user_id` column. For example, when the `user_id` is used in all models we can check if the `user_id` of `model a` matches `user_id` of `model b` before saving the records - if the IDs are different, an error will be attached to the errors hash of `self`.
|
4
4
|
|
5
5
|
## Requirements
|
6
6
|
ruby >= 1.9
|
@@ -23,21 +23,18 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
## Usage
|
25
25
|
|
26
|
-
Call `validate_foreign_keys` below the association definitions (`belongs_to`, ...) in your model. By default it assumes that it should check all
|
26
|
+
Call `validate_foreign_keys` below the association definitions (`belongs_to`, ...) in your model. By default it assumes that it should check all `belongs_to` relations against the `user_id` column. So any relation will be checked for a matching `user_id`.
|
27
27
|
|
28
28
|
Change behaviour by calling `validate_foreign_keys` with arguments hash.
|
29
29
|
|
30
30
|
validate_foreign_keys on: :admin_user, with: [:project]
|
31
31
|
|
32
|
-
This would only check `model.project.admin_user_id` to match `model.admin_user_id
|
32
|
+
This would only check `model.project.admin_user_id` to match `model.admin_user_id` before saving the record.
|
33
33
|
|
34
34
|
## Note
|
35
35
|
|
36
36
|
Only tested with ActiveRecord
|
37
37
|
|
38
|
-
## TODO
|
39
|
-
|
40
|
-
- Tests!
|
41
38
|
|
42
39
|
## Contributing
|
43
40
|
|
@@ -4,27 +4,31 @@ module ForeignKeyValidation
|
|
4
4
|
|
5
5
|
included do
|
6
6
|
private
|
7
|
-
def validate_foreign_key(
|
8
|
-
return if send(
|
7
|
+
def validate_foreign_key(validate_against_key, reflection_name)
|
8
|
+
return if send(reflection_name).try(validate_against_key).nil? or try(validate_against_key).nil?
|
9
9
|
|
10
|
-
if send(
|
11
|
-
errors.add(
|
10
|
+
if send(reflection_name).send(validate_against_key) != send(validate_against_key)
|
11
|
+
errors.add(validate_against_key, "#{validate_against_key} of #{reflection_name} does not match #{self.class.name.tableize} #{validate_against_key}")
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
module ClassMethods
|
17
17
|
def validate_foreign_keys(opt={})
|
18
|
-
|
19
|
-
reflections = reflect_on_all_associations(:belongs_to).map(&:name).map(&:to_s)
|
20
|
-
validate_with = ((Array(opt[:with]).map(&:to_s) if opt[:with]) || reflections).reject {|n| n == validate_against}
|
18
|
+
subclasses.map {|klass| klass.send(:validate_foreign_keys, opt) } if subclasses.any?
|
21
19
|
|
22
|
-
|
23
|
-
|
20
|
+
validate_against = (opt[:on] || :user).to_s
|
21
|
+
validate_against_key = "#{validate_against}_id"
|
22
|
+
reflection_names = reflect_on_all_associations(:belongs_to).map(&:name).map(&:to_s)
|
23
|
+
validate_with = ((Array(opt[:with]).map(&:to_s) if opt[:with]) || reflection_names).reject {|n| n == validate_against}
|
24
|
+
|
25
|
+
raise ArgumentError, "Can't find any belongs_to relations for #{name} class. Put validation call below association definitions" if reflection_names.empty?
|
26
|
+
raise ArgumentError, "No foreign key #{validate_against_key} on #{table_name} table!" unless reflection_names.include?(validate_against)
|
27
|
+
raise ArgumentError, "Unknown relation in #{validate_with}!" unless validate_with.all? {|k| reflection_names.include?(k) }
|
24
28
|
|
25
29
|
define_method "validate_foreign_keys_on_#{validate_against}" do
|
26
|
-
validate_with.each do |
|
27
|
-
validate_foreign_key(
|
30
|
+
validate_with.each do |reflection_name|
|
31
|
+
validate_foreign_key(validate_against_key, reflection_name)
|
28
32
|
end
|
29
33
|
end
|
30
34
|
private "validate_foreign_keys_on_#{validate_against}".to_sym
|
data/spec/models/model_spec.rb
CHANGED
@@ -5,13 +5,16 @@ describe ForeignKeyValidation::ModelExtension do
|
|
5
5
|
# NOTE: it's important to not create the objects through relation (user.projects.create...)
|
6
6
|
# it looks like active_record is caching the classes - but we need to test different class configs
|
7
7
|
|
8
|
-
context "
|
8
|
+
context "without calling validation" do
|
9
9
|
|
10
10
|
let(:user) { User.create }
|
11
11
|
let(:project) { Project.create user: user }
|
12
12
|
let(:idea) { Idea.create user: user, project: project }
|
13
13
|
let(:issue) { Issue.create user: user, project: project }
|
14
14
|
let(:comment) { Comment.create user: user, issue: issue }
|
15
|
+
# sti model
|
16
|
+
let(:manager) { Manager.create user: user }
|
17
|
+
let(:developer) { Developer.create user: user, boss: manager }
|
15
18
|
|
16
19
|
it "uses same user ids by default" do
|
17
20
|
expect(project.user_id).to eq(user.id)
|
@@ -46,14 +49,22 @@ describe ForeignKeyValidation::ModelExtension do
|
|
46
49
|
expect(comment.user_id).to eq(42)
|
47
50
|
end
|
48
51
|
|
52
|
+
it "allow to rewrite user id of developer" do
|
53
|
+
developer.user_id = 42
|
54
|
+
developer.save
|
55
|
+
developer.reload
|
56
|
+
expect(developer.user_id).to eq(42)
|
57
|
+
end
|
58
|
+
|
49
59
|
end
|
50
60
|
|
51
|
-
context "
|
61
|
+
context "with calling validation" do
|
52
62
|
before do
|
53
63
|
Idea.send :validate_foreign_keys
|
54
64
|
Project.send :validate_foreign_keys
|
55
65
|
Issue.send :validate_foreign_keys
|
56
66
|
Comment.send :validate_foreign_keys
|
67
|
+
Member.send :validate_foreign_keys # sti model
|
57
68
|
end
|
58
69
|
|
59
70
|
let(:user) { User.create }
|
@@ -61,6 +72,9 @@ describe ForeignKeyValidation::ModelExtension do
|
|
61
72
|
let(:idea) { Idea.create user: user, project: project }
|
62
73
|
let(:issue) { Issue.create user: user, project: project }
|
63
74
|
let(:comment) { Comment.create user: user, issue: issue }
|
75
|
+
# sti model
|
76
|
+
let(:manager) { Manager.create user: user }
|
77
|
+
let(:developer) { Developer.create user: user, boss: manager }
|
64
78
|
|
65
79
|
it "uses same user ids by default" do
|
66
80
|
expect(project.user_id).to eq(user.id)
|
@@ -70,21 +84,21 @@ describe ForeignKeyValidation::ModelExtension do
|
|
70
84
|
it "does not allow to rewrite user id of idea" do
|
71
85
|
idea.user_id = 42
|
72
86
|
idea.save
|
73
|
-
expect(idea.errors.messages.values.flatten).to include("
|
87
|
+
expect(idea.errors.messages.values.flatten).to include("user_id of project does not match ideas user_id")
|
74
88
|
expect(idea.reload.user_id).to_not eq(42)
|
75
89
|
end
|
76
90
|
|
77
91
|
it "does not allow to rewrite user id of issue" do
|
78
92
|
issue.user_id = 42
|
79
93
|
issue.save
|
80
|
-
expect(issue.errors.messages.values.flatten).to include("
|
94
|
+
expect(issue.errors.messages.values.flatten).to include("user_id of project does not match issues user_id")
|
81
95
|
expect(issue.reload.user_id).to_not eq(42)
|
82
96
|
end
|
83
97
|
|
84
98
|
it "does not allow to rewrite user id of comment" do
|
85
99
|
comment.user_id = 42
|
86
100
|
comment.save
|
87
|
-
expect(comment.errors.messages.values.flatten).to include("
|
101
|
+
expect(comment.errors.messages.values.flatten).to include("user_id of issue does not match comments user_id")
|
88
102
|
expect(comment.reload.user_id).to_not eq(42)
|
89
103
|
end
|
90
104
|
|
@@ -95,6 +109,13 @@ describe ForeignKeyValidation::ModelExtension do
|
|
95
109
|
expect(project.reload.user_id).to eq(42)
|
96
110
|
end
|
97
111
|
|
112
|
+
it "does not allow to rewrite user id of developer" do
|
113
|
+
developer.user_id = 42
|
114
|
+
developer.save
|
115
|
+
expect(developer.errors.messages.values.flatten).to include("user_id of boss does not match developers user_id")
|
116
|
+
expect(developer.reload.user_id).to_not eq(42)
|
117
|
+
end
|
118
|
+
|
98
119
|
it "does not allow to call private validate_foreign_key method" do
|
99
120
|
expect{issue.validate_foreign_key("test", "unrat")}.to raise_exception(/private method `validate_foreign_key' called/)
|
100
121
|
end
|
@@ -105,7 +126,7 @@ describe ForeignKeyValidation::ModelExtension do
|
|
105
126
|
|
106
127
|
end
|
107
128
|
|
108
|
-
context "
|
129
|
+
context "with calling validation with attributes hash" do
|
109
130
|
before do
|
110
131
|
Idea.class_eval do
|
111
132
|
validate_foreign_keys on: :user, with: :project
|
@@ -126,7 +147,7 @@ describe ForeignKeyValidation::ModelExtension do
|
|
126
147
|
it "does not allow to rewrite user id of idea" do
|
127
148
|
idea.user_id = 42
|
128
149
|
idea.save
|
129
|
-
expect(idea.errors.messages.values.flatten).to include("
|
150
|
+
expect(idea.errors.messages.values.flatten).to include("user_id of project does not match ideas user_id")
|
130
151
|
expect(idea.reload.user_id).to_not eq(42)
|
131
152
|
end
|
132
153
|
|
@@ -153,24 +174,28 @@ describe ForeignKeyValidation::ModelExtension do
|
|
153
174
|
|
154
175
|
end
|
155
176
|
|
156
|
-
context "
|
157
|
-
|
158
|
-
let(:user) { User.create }
|
159
|
-
let(:project) { Project.create user: user }
|
160
|
-
let(:issue) { Issue.create user: user, project: project }
|
161
|
-
let(:comment) { Comment.create user: user, issue: issue }
|
177
|
+
context "with calling validation and wrong attributes hash" do
|
162
178
|
|
163
179
|
it "raises error due to wrong :on key" do
|
164
|
-
expect{Idea.class_eval { validate_foreign_keys on: :
|
180
|
+
expect{Idea.class_eval { validate_foreign_keys on: :not_existing }}.to raise_error("No foreign key not_existing_id on ideas table!")
|
165
181
|
end
|
166
182
|
|
167
183
|
it "raises error due to wrong :with key" do
|
168
|
-
expect{Idea.class_eval { validate_foreign_keys with: :
|
184
|
+
expect{Idea.class_eval { validate_foreign_keys with: :not_existing }}.to raise_error('Unknown relation in ["not_existing"]!')
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
context "with calling validation and missing relations" do
|
190
|
+
|
191
|
+
it "raises error due to wrong :on key" do
|
192
|
+
expect{Dummy.class_eval { validate_foreign_keys }}.to raise_error("Can't find any belongs_to relations for Dummy class. Put validation call below association definitions")
|
169
193
|
end
|
170
194
|
|
171
195
|
end
|
172
196
|
|
173
|
-
|
197
|
+
|
198
|
+
context "with calling validation and missing foreign key on self" do
|
174
199
|
|
175
200
|
before do
|
176
201
|
Issue.class_eval do
|
@@ -185,13 +210,13 @@ describe ForeignKeyValidation::ModelExtension do
|
|
185
210
|
it "does not allow to rewrite user id of issue" do
|
186
211
|
issue.user_id = 42
|
187
212
|
issue.save
|
188
|
-
expect(issue.errors.messages.values.flatten).to include("
|
213
|
+
expect(issue.errors.messages.values.flatten).to include("user_id of project does not match issues user_id")
|
189
214
|
expect(issue.reload.user_id).to_not eq(42)
|
190
215
|
end
|
191
216
|
|
192
217
|
end
|
193
218
|
|
194
|
-
context "
|
219
|
+
context "with calling validation and missing foreign key on relation" do
|
195
220
|
|
196
221
|
before do
|
197
222
|
Issue.class_eval do
|
data/spec/support/load_models.rb
CHANGED
@@ -3,10 +3,16 @@ require "active_record"
|
|
3
3
|
class User < ActiveRecord::Base
|
4
4
|
has_many :projects
|
5
5
|
has_many :ideas
|
6
|
+
has_many :managers
|
7
|
+
has_many :developers
|
8
|
+
has_many :comments
|
9
|
+
has_many :issues
|
10
|
+
has_many :ideas
|
6
11
|
end
|
7
12
|
|
8
13
|
class Project < ActiveRecord::Base
|
9
14
|
belongs_to :user
|
15
|
+
belongs_to :member
|
10
16
|
has_many :ideas
|
11
17
|
has_many :issues
|
12
18
|
end
|
@@ -26,3 +32,19 @@ class Comment < ActiveRecord::Base
|
|
26
32
|
belongs_to :issue
|
27
33
|
belongs_to :user
|
28
34
|
end
|
35
|
+
|
36
|
+
class Dummy < ActiveRecord::Base
|
37
|
+
end
|
38
|
+
|
39
|
+
class Member < ActiveRecord::Base
|
40
|
+
belongs_to :user
|
41
|
+
has_many :projects
|
42
|
+
end
|
43
|
+
|
44
|
+
class Manager < Member
|
45
|
+
has_many :developers
|
46
|
+
end
|
47
|
+
|
48
|
+
class Developer < Member
|
49
|
+
belongs_to :boss, class_name: "Manager", foreign_key: :boss_id
|
50
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
|
-
Object.send(:remove_const, :User)
|
2
|
-
Object.send(:remove_const, :Project)
|
3
|
-
Object.send(:remove_const, :Idea)
|
4
|
-
Object.send(:remove_const, :Issue)
|
5
|
-
Object.send(:remove_const, :Comment)
|
1
|
+
Object.send(:remove_const, :User) if Object.constants.include?(:User)
|
2
|
+
Object.send(:remove_const, :Project) if Object.constants.include?(:Project)
|
3
|
+
Object.send(:remove_const, :Idea) if Object.constants.include?(:Idea)
|
4
|
+
Object.send(:remove_const, :Issue) if Object.constants.include?(:Issue)
|
5
|
+
Object.send(:remove_const, :Comment) if Object.constants.include?(:Comment)
|
6
|
+
Object.send(:remove_const, :Member) if Object.constants.include?(:Member)
|
7
|
+
Object.send(:remove_const, :Developer) if Object.constants.include?(:Developer)
|
8
|
+
Object.send(:remove_const, :Manager) if Object.constants.include?(:Manager)
|
data/spec/support/schema.rb
CHANGED
@@ -6,6 +6,7 @@ ActiveRecord::Schema.define do
|
|
6
6
|
|
7
7
|
create_table "projects", force: true do |t|
|
8
8
|
t.integer "user_id"
|
9
|
+
t.integer "member_id"
|
9
10
|
end
|
10
11
|
|
11
12
|
create_table "ideas", force: true do |t|
|
@@ -23,4 +24,13 @@ ActiveRecord::Schema.define do
|
|
23
24
|
t.integer "issue_id"
|
24
25
|
end
|
25
26
|
|
27
|
+
create_table "dummies", force: true do |t|
|
28
|
+
end
|
29
|
+
|
30
|
+
create_table "members", force: true do |t|
|
31
|
+
t.integer "user_id"
|
32
|
+
t.integer "boss_id"
|
33
|
+
t.string "type"
|
34
|
+
end
|
35
|
+
|
26
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreign_key_validation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcus Geißler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|