activerecord-exclusive-arc 0.2.1 → 0.2.3

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: 27e01ec54aa223f1e36904771298125b451731632cca44ae6d0d15627cbcf879
4
- data.tar.gz: 97990f81b596836909e67609a2d9745d6be13f0852373829656d2218454f6029
3
+ metadata.gz: 315a60d914bac0fcce5129cddb165afa90604355e2c4bc9c46cad1b2d5aac79a
4
+ data.tar.gz: 5b93a4be4ddba10b47eaaaddbd5ba7cb5df072b2462114e01945e2c2f9b051d6
5
5
  SHA512:
6
- metadata.gz: 448c3910176f2f9163d0cfac054482fa2c6dc96b3e67d1093668626e2dbc94cfdeb59adcc371f3890af35541f9e4db7b8f4db0cd0108db09cd6bfe68de4954be
7
- data.tar.gz: ec36d41ce0896b6505f76eb5112dd999db1cb334d9d0b956d82d7f043efba1e4999ef32ddbf0a384c140b5dcba01dfbaf7606b4638ac94381fbc8408751a7d3a
6
+ metadata.gz: 502088f01538ad5c31fbb4052f2a03c7f4dbe5723533d452b07abb331dae7d20c6dc4b87f3c5e019731c2b6cfce43834726f4cc89febf32e971593d2ecb39e44
7
+ data.tar.gz: 74c62d1c094c033ff0224af98128fe0d65a114e1f36099845cf1d3312cbd39d34a73db71f65b275eda92746be51d063e62ca65feb31849578833951af8115976
data/README.md CHANGED
@@ -1,31 +1,31 @@
1
- ### What does it do?
1
+ ## 💫 `activerecord-exclusive-arc` 💫
2
2
 
3
- It allows an ActiveRecord model to exclusively belong to one of any number of different types of ActiveRecord
4
- models.
3
+ A RubyGem that allows an ActiveRecord model to exclusively belong to one of any number of different
4
+ types of ActiveRecord models.
5
5
 
6
- ### Doesn’t Rails already provide this?
6
+ ### Doesn’t Rails already provide a way to do this?
7
7
 
8
- It does, but there are decent arguments against the default Rails way of doing polymorphism. Consider the
9
- fact that the Ruby class name is stored in the database as a string. If you want to change the name of the
10
- Ruby class used for such reasons, you must also update the database strings that represent it. The bleeding
11
- of application-layer definitions into the database may become a liability.
8
+ It does, but there are decent arguments against the default Rails way of doing polymorphism.
9
+ Consider the fact that the Ruby class name is stored in the database as a string. If you want to
10
+ change the name of the Ruby class used for such reasons, you must also update the database strings
11
+ that represent it. The seeping of application-layer definitions into the database may become a
12
+ liability.
12
13
 
13
- Another common argument concerns referential integrity. _Foreign Key Constraints_ are a common mechanism to
14
- ensure primary keys of tables can be reliably used as foreign keys on others. This becomes harder to enforce
15
- when a column that represents a Ruby class is one of the components required for unique identification.
16
-
17
- There are also quality of life considerations, such as not being able to eager-load the `belongs_to ...
18
- polymorphic: true` relationship and the fact that polymorphic indexes require multiple columns.
14
+ Another common argument concerns referential integrity. _Foreign Key Constraints_ are a common
15
+ mechanism to ensure primary keys of database tables can be reliably used as foreign keys on others.
16
+ This becomes harder to enforce in the databse when a string column that represents a Ruby class is
17
+ one of the components required for unique identification.
19
18
 
20
19
  ### So how does this work?
21
20
 
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
- polymorphic methods, and an `ActiveRecord` validation for good measure.
21
+ It reduces the boilerplate of managing a _Polymorphic Assication_ modeled as a pattern called an
22
+ _Exclusive Arc_, where each potential polymorphic reference has its own foreign key. This maps
23
+ nicely to a set of optional `belongs_to` relationships, some polymorphic convenience methods, and a
24
+ database check constraint with a matching `ActiveRecord` validation.
25
25
 
26
26
  ## How to use
27
27
 
28
- Firstly, in your `Gemfile`:
28
+ Firstly, add the gem to your `Gemfile` and `bundle install`:
29
29
 
30
30
  ```ruby
31
31
  gem "activerecord-exclusive-arc"
@@ -37,8 +37,8 @@ The feature set of this gem is offered via a Rails generator command:
37
37
  bin/rails g exclusive_arc <Model> <arc> <belongs_to1> <belongs_to2> ...
38
38
  ```
39
39
 
40
- This assumes you already have a `<Model>`. The `<arc>` is the name of the polymorphic association you want to
41
- establish that may either be a `<belongs_to1>`, `<belongs_to2>`, etc. Say we ran:
40
+ This assumes you already have a `<Model>`. The `<arc>` is the name of the polymorphic association
41
+ you want to establish that may either be a `<belongs_to1>`, `<belongs_to2>`, etc. Say we ran:
42
42
 
43
43
  ```
44
44
  bin/rails g exclusive_arc Comment commentable post comment
@@ -84,7 +84,8 @@ class Comment < ApplicationRecord
84
84
  end
85
85
  ```
86
86
 
87
- Continuing with our example, the generator command would also produce a migration that looks like this:
87
+ Continuing with our example, the generator command would also produce a migration that looks like
88
+ this:
88
89
 
89
90
  ```ruby
90
91
  class CommentCommentableExclusiveArc < ActiveRecord::Migration[7.0]
@@ -100,9 +101,9 @@ class CommentCommentableExclusiveArc < ActiveRecord::Migration[7.0]
100
101
  end
101
102
  ```
102
103
 
103
- The check constraint ensures `ActiveRecord` validations can’t be bypassed to break the fabeled rule - "There
104
- Can Only Be One™️". Traditional foreign key constraints can be used and the partial indexes provide improved
105
- lookup performance for each individual polymorphic assoication.
104
+ The check constraint ensures `ActiveRecord` validations can’t be bypassed to break the fabeled
105
+ rule - "There Can Only Be One". Traditional foreign key constraints can be used and the partial
106
+ indexes provide improved lookup performance for each individual polymorphic assoication.
106
107
 
107
108
  ### Exclusive Arc Options
108
109
 
@@ -114,30 +115,24 @@ Usage:
114
115
  rails generate exclusive_arc NAME [arc belongs_to1 belongs_to2 ...] [options]
115
116
 
116
117
  Options:
117
- [--skip-namespace], [--no-skip-namespace] # Skip namespace (affects only isolated engines)
118
- [--skip-collision-check], [--no-skip-collision-check] # Skip collision check
119
118
  [--optional], [--no-optional] # Exclusive arc is optional
120
119
  [--skip-foreign-key-constraints], [--no-skip-foreign-key-constraints] # Skip foreign key constraints
121
120
  [--skip-foreign-key-indexes], [--no-skip-foreign-key-indexes] # Skip foreign key partial indexes
122
121
  [--skip-check-constraint], [--no-skip-check-constraint] # Skip check constraint
123
122
 
124
- Runtime options:
125
- -f, [--force] # Overwrite files that already exist
126
- -p, [--pretend], [--no-pretend] # Run but do not make any changes
127
- -q, [--quiet], [--no-quiet] # Suppress status output
128
- -s, [--skip], [--no-skip] # Skip files that already exist
129
-
130
123
  Adds an Exclusive Arc to an ActiveRecord model and generates the migration for it
131
124
  ```
132
125
 
133
- Notably, if you want to make an Exclusive Arc optional, you can use the `--optional` flag. This will adjust
134
- the definition in your `ActiveRecord` model and loosen both the validation and database check constraint so
135
- that there can be 0 or 1 foreign keys set for the polymorphic association.
126
+ Notably, if you want to make an Exclusive Arc optional, you can use the `--optional` flag. This will
127
+ adjust the definition in your `ActiveRecord` model and loosen both the validation and database check
128
+ constraint so that there can be 0 or 1 foreign keys set for the polymorphic reference.
136
129
 
137
130
  ### Compatibility
138
131
 
139
- Currently `activerecord-exclusive-arc` is tested against a matrix of Ruby 2.7 and 3.2, Rails 6.1 and 7.0, and
140
- `postgresql` and `sqlite3` database adapters.
132
+ Currently `activerecord-exclusive-arc` is tested against a matrix of:
133
+ * Ruby 2.7 and 3.2
134
+ * Rails 6.1 and 7.0
135
+ * `postgresql` and `sqlite3` database adapters
141
136
 
142
137
  ### Contributing
143
138
 
@@ -1,3 +1,3 @@
1
1
  module ExclusiveArc
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.3"
3
3
  end
@@ -31,10 +31,16 @@ class ExclusiveArcGenerator < ActiveRecord::Generators::Base
31
31
  model_file_path,
32
32
  class_name.demodulize,
33
33
  <<~RB
34
- #{indents}include ExclusiveArc::Model
35
34
  #{indents}has_exclusive_arc #{model_exclusive_arcs}
36
35
  RB
37
36
  )
37
+ inject_into_class(
38
+ model_file_path,
39
+ class_name.demodulize,
40
+ <<~RB
41
+ #{indents}include ExclusiveArc::Model
42
+ RB
43
+ )
38
44
  end
39
45
 
40
46
  no_tasks do
@@ -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.2.1
4
+ version: 0.2.3
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-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
89
  - !ruby/object:Gem::Version
90
90
  version: '0'
91
91
  requirements: []
92
- rubygems_version: 3.4.10
92
+ rubygems_version: 3.4.12
93
93
  signing_key:
94
94
  specification_version: 4
95
95
  summary: An ActiveRecord extension for polymorphic exclusive arc relationships