foreign_key_validation 0.0.6 → 0.0.7
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.
- 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
|