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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5dbceaa69bcb956d89d1c636209771640242741ae2e06f57a038f474f64825d8
4
- data.tar.gz: a8319a8c41ea99543fc8d52387adb35aa6a56b264adfdfa39a25b8b6d603f774
3
+ metadata.gz: b3824d9259213c59c55ee5287cc9bd3b8e76924be405a269c6752434811c2599
4
+ data.tar.gz: 2e8b6ad42b5b4cad774e7ef56e21c54fa9a0e8223fce05762ecb60cf791b4b87
5
5
  SHA512:
6
- metadata.gz: c0c2ab91655256c9226c3775d2e6ee1c05d733089110728ade4aa39416e23f15a7a048f6e2189fc23aa9aac93783180ac8eabca57f404808957a044375c9ee8d
7
- data.tar.gz: ca725edf128c121af9e4a5e8880b8a8895ce8eefabf0ae9175d7c0054a1c7f38475f8b8e47ede79cd31db64fc63a697e97c28525c63062a21b148e042eaefadf
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. *Foreign Key Constraints* are a common mechanism to
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 *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
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
- exclusive_arc :commentable, [:post, :comment]
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
-
@@ -8,27 +8,39 @@ module ExclusiveArc
8
8
  end
9
9
 
10
10
  class_methods do
11
- def exclusive_arc(*args)
12
- arcs = args[0].is_a?(Hash) ? args[0] : {args[0] => args[1]}
13
- options = args[2] || {}
14
- arcs.each do |(name, belong_tos)|
15
- belong_tos.map { |option| belongs_to(option, optional: true) }
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
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
22
- def #{name}
23
- #{belong_tos.join(" || ")}
24
- end
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
- def #{name}=(polymorphic)
27
- assign_exclusive_arc(:#{name}, polymorphic)
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.each do |name, reflection|
40
- public_send("#{name}=", polymorphic.is_a?(reflection.klass) ? polymorphic : nil)
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
@@ -1,3 +1,3 @@
1
1
  module ExclusiveArc
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -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
- string = "add_reference :#{table_name}, :#{reference}"
49
- type = reference_type(reference)
50
- string += ", type: :#{type}" unless /int/.match?(type.downcase)
51
- string += ", foreign_key: true" unless options[:skip_foreign_key_constraints]
52
- string += ", index: {where: \"#{reference}_id IS NOT NULL\"}" unless options[:skip_foreign_key_indexes]
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 = reference.singularize.classify.constantize
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}_id IS NULL THEN 0 ELSE 1 END"
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
- <% belong_tos.each do |reference| %><%= add_reference(reference) %>
4
- <% end %><% unless options[:skip_check_constraint] %>add_check_constraint(
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.1.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-08 00:00:00.000000000 Z
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