activerecord-exclusive-arc 0.1.0 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -5
- data/lib/exclusive_arc/model.rb +30 -17
- data/lib/exclusive_arc/version.rb +1 -1
- data/lib/generators/exclusive_arc_generator.rb +55 -8
- data/lib/generators/templates/migration.rb.erb +2 -6
- metadata +2 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3824d9259213c59c55ee5287cc9bd3b8e76924be405a269c6752434811c2599
|
4
|
+
data.tar.gz: 2e8b6ad42b5b4cad774e7ef56e21c54fa9a0e8223fce05762ecb60cf791b4b87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28d0736dfd28b87e367ab62d9ac1c39cc556bc76d999b390a4b6220b88ac566bcac66c6fda36b0237a5442180b44602c72e6ef837ed99a4f281b0e42da4aac81
|
7
|
+
data.tar.gz: e62da6627153c7853dde9bba360ec654ea28baf088bb4db6261fc77dd88fc78d1bf4b5f5d2c2d777682d0233e7f6ab72567ed1790e8813670b4964ec69a063a8
|
data/README.md
CHANGED
@@ -10,7 +10,7 @@ fact that the Ruby class name is stored in the database as a string. If you want
|
|
10
10
|
Ruby class used for such reasons, you must also update the database strings that represent it. The bleeding
|
11
11
|
of application-layer definitions into the database may become a liability.
|
12
12
|
|
13
|
-
Another common argument concerns referential integrity.
|
13
|
+
Another common argument concerns referential integrity. _Foreign Key Constraints_ are a common mechanism to
|
14
14
|
ensure primary keys of tables can be reliably used as foreign keys on others. This becomes harder to enforce
|
15
15
|
when a column that represents a Ruby class is one of the components required for unique identification.
|
16
16
|
|
@@ -19,8 +19,8 @@ polymorphic: true` relationship and the fact that polymorphic indexes require mu
|
|
19
19
|
|
20
20
|
### So how does this work?
|
21
21
|
|
22
|
-
It reduces the boilerplate of managing a
|
23
|
-
|
22
|
+
It reduces the boilerplate of managing a _Polymorphic Assication_ modeled as a pattern called an _Exclusive
|
23
|
+
Arc_. This maps nicely to a database constraint, a set of optional `belongs_to` relationships, some
|
24
24
|
polymorphic methods, and an `ActiveRecord` validation for good measure.
|
25
25
|
|
26
26
|
## How to use
|
@@ -49,7 +49,7 @@ This will inject code into your `Comment` Model:
|
|
49
49
|
```ruby
|
50
50
|
class Comment < ApplicationRecord
|
51
51
|
include ExclusiveArc::Model
|
52
|
-
|
52
|
+
has_exclusive_arc :commentable, [:post, :comment]
|
53
53
|
end
|
54
54
|
```
|
55
55
|
|
@@ -73,6 +73,17 @@ end
|
|
73
73
|
|
74
74
|
It's a bit more involved than that, but it demonstrates the essense of the API as an `ActiveRecord` user.
|
75
75
|
|
76
|
+
If you need to customize a specific `belongs_to` relationship, you can do so by declaring it before
|
77
|
+
`has_exclusive_arc`:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class Comment < ApplicationRecord
|
81
|
+
include ExclusiveArc::Model
|
82
|
+
belongs_to :post, -> { where(comments_enabled: true) }, optional: true
|
83
|
+
has_exclusive_arc :commentable, [:post, :comment]
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
76
87
|
Continuing with our example, the generator command would also produce a migration that looks like this:
|
77
88
|
|
78
89
|
```ruby
|
@@ -135,4 +146,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/waymon
|
|
135
146
|
### License
|
136
147
|
|
137
148
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
138
|
-
|
data/lib/exclusive_arc/model.rb
CHANGED
@@ -8,27 +8,39 @@ module ExclusiveArc
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class_methods do
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
exclusive_arcs[name] = Definition.new(
|
17
|
-
reflections: reflections.slice(*belong_tos.map(&:to_s)),
|
18
|
-
options: options
|
19
|
-
)
|
11
|
+
def has_exclusive_arc(*args)
|
12
|
+
arc = args[0]
|
13
|
+
belong_tos = args[1]
|
14
|
+
belong_tos.map do |option|
|
15
|
+
next if reflections[option.to_s]
|
20
16
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
belongs_to(option, optional: true)
|
18
|
+
end
|
19
|
+
exclusive_arcs[arc] = Definition.new(
|
20
|
+
reflections: reflections.slice(*belong_tos.map(&:to_s)),
|
21
|
+
options: args[2] || {}
|
22
|
+
)
|
25
23
|
|
26
|
-
|
27
|
-
|
24
|
+
belong_tos.each do |option|
|
25
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
26
|
+
def #{option}=(polymorphic)
|
27
|
+
@#{arc} = nil unless @#{arc} == polymorphic
|
28
|
+
super
|
28
29
|
end
|
29
30
|
RUBY
|
30
31
|
end
|
31
32
|
|
33
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
34
|
+
def #{arc}
|
35
|
+
@#{arc} ||= (#{belong_tos.join(" || ")})
|
36
|
+
end
|
37
|
+
|
38
|
+
def #{arc}=(polymorphic)
|
39
|
+
assign_exclusive_arc(:#{arc}, polymorphic)
|
40
|
+
@#{arc} = polymorphic
|
41
|
+
end
|
42
|
+
RUBY
|
43
|
+
|
32
44
|
validate :validate_exclusive_arcs
|
33
45
|
end
|
34
46
|
end
|
@@ -36,9 +48,10 @@ module ExclusiveArc
|
|
36
48
|
private
|
37
49
|
|
38
50
|
def assign_exclusive_arc(arc, polymorphic)
|
39
|
-
exclusive_arcs.fetch(arc).reflections.
|
40
|
-
|
51
|
+
attributes = exclusive_arcs.fetch(arc).reflections.to_h do |name, reflection|
|
52
|
+
[name, polymorphic.is_a?(reflection.klass) ? polymorphic : nil]
|
41
53
|
end
|
54
|
+
assign_attributes attributes
|
42
55
|
end
|
43
56
|
|
44
57
|
def validate_exclusive_arcs
|
@@ -27,12 +27,18 @@ class ExclusiveArcGenerator < ActiveRecord::Generators::Base
|
|
27
27
|
|
28
28
|
def inject_exclusive_arc_into_model
|
29
29
|
indents = " " * (class_name.scan("::").count + 1)
|
30
|
+
inject_into_class(
|
31
|
+
model_file_path,
|
32
|
+
class_name.demodulize,
|
33
|
+
<<~RB
|
34
|
+
#{indents}has_exclusive_arc #{model_exclusive_arcs}
|
35
|
+
RB
|
36
|
+
)
|
30
37
|
inject_into_class(
|
31
38
|
model_file_path,
|
32
39
|
class_name.demodulize,
|
33
40
|
<<~RB
|
34
41
|
#{indents}include ExclusiveArc::Model
|
35
|
-
#{indents}exclusive_arc #{model_exclusive_arcs}
|
36
42
|
RB
|
37
43
|
)
|
38
44
|
end
|
@@ -44,12 +50,30 @@ class ExclusiveArcGenerator < ActiveRecord::Generators::Base
|
|
44
50
|
string
|
45
51
|
end
|
46
52
|
|
53
|
+
def add_references
|
54
|
+
belong_tos.map do |reference|
|
55
|
+
add_reference(reference)
|
56
|
+
end.join("\n")
|
57
|
+
end
|
58
|
+
|
47
59
|
def add_reference(reference)
|
48
|
-
|
49
|
-
type = reference_type(reference)
|
50
|
-
|
51
|
-
|
52
|
-
|
60
|
+
foreign_key = foreign_key_name(reference)
|
61
|
+
type = reference_type(reference).downcase
|
62
|
+
if foreign_key == "#{reference}_id"
|
63
|
+
string = " add_reference :#{table_name}, :#{reference}"
|
64
|
+
string += ", type: :#{type}" unless /int/.match?(type)
|
65
|
+
string += ", foreign_key: true" unless options[:skip_foreign_key_constraints]
|
66
|
+
string += ", index: {where: \"#{foreign_key} IS NOT NULL\"}" unless options[:skip_foreign_key_indexes]
|
67
|
+
else
|
68
|
+
string = " add_column :#{table_name}, :#{foreign_key}, :#{type}"
|
69
|
+
unless options[:skip_foreign_key_constraints]
|
70
|
+
referenced_table_name = reference_table_name(reference)
|
71
|
+
string += "\n add_foreign_key :#{table_name}, :#{referenced_table_name}, column: :#{foreign_key}"
|
72
|
+
end
|
73
|
+
unless options[:skip_foreign_key_indexes]
|
74
|
+
string += "\n add_index :#{table_name}, :#{foreign_key}, where: \"#{foreign_key} IS NOT NULL\""
|
75
|
+
end
|
76
|
+
end
|
53
77
|
string
|
54
78
|
end
|
55
79
|
|
@@ -62,15 +86,38 @@ class ExclusiveArcGenerator < ActiveRecord::Generators::Base
|
|
62
86
|
end
|
63
87
|
|
64
88
|
def reference_type(reference)
|
65
|
-
klass =
|
89
|
+
klass = class_name.constantize.reflections[reference].klass
|
66
90
|
klass.columns.find { |col| col.name == klass.primary_key }.sql_type
|
67
91
|
rescue
|
68
92
|
"bigint"
|
69
93
|
end
|
70
94
|
|
95
|
+
def reference_table_name(reference)
|
96
|
+
class_name.constantize.reflections[reference].klass.table_name
|
97
|
+
rescue
|
98
|
+
reference.tableize
|
99
|
+
end
|
100
|
+
|
101
|
+
def foreign_key_name(reference)
|
102
|
+
class_name.constantize.reflections[reference].foreign_key
|
103
|
+
rescue
|
104
|
+
"#{reference}_id"
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_check_constraint
|
108
|
+
return if options[:skip_check_constraint]
|
109
|
+
<<-RUBY
|
110
|
+
add_check_constraint(
|
111
|
+
:#{table_name},
|
112
|
+
"#{check_constraint}",
|
113
|
+
name: :#{arc}
|
114
|
+
)
|
115
|
+
RUBY
|
116
|
+
end
|
117
|
+
|
71
118
|
def check_constraint
|
72
119
|
reference_checks = belong_tos.map do |reference|
|
73
|
-
"CASE WHEN #{reference}
|
120
|
+
"CASE WHEN #{foreign_key_name(reference)} IS NULL THEN 0 ELSE 1 END"
|
74
121
|
end
|
75
122
|
condition = options[:optional] ? "<= 1" : "= 1"
|
76
123
|
"(#{reference_checks.join(" + ")}) #{condition}"
|
@@ -1,10 +1,6 @@
|
|
1
1
|
class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
2
2
|
def change
|
3
|
-
|
4
|
-
|
5
|
-
:<%= table_name %>,
|
6
|
-
"<%= check_constraint %>",
|
7
|
-
name: :<%= arc %>
|
8
|
-
)<% end %>
|
3
|
+
<%= add_references %>
|
4
|
+
<%= add_check_constraint %>
|
9
5
|
end
|
10
6
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-exclusive-arc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- justin talbott
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -50,26 +50,6 @@ dependencies:
|
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: '8'
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: railties
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - ">="
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '6.1'
|
60
|
-
- - "<"
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '8'
|
63
|
-
type: :runtime
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '6.1'
|
70
|
-
- - "<"
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
version: '8'
|
73
53
|
description:
|
74
54
|
email:
|
75
55
|
- gmail@justintalbott.com
|