activerecord-exclusive-arc 0.2.1 → 0.2.3

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