activerecord-exclusive-arc 0.1.0 → 0.2.2

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 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