activerecord-polytypes 0.1.0 → 0.1.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 +4 -4
- data/CODE_OF_CONDUCT.md +12 -12
- data/README.md +24 -3
- data/lib/activerecord-polytypes/version.rb +1 -1
- data/lib/activerecord-polytypes.rb +45 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14bd860961bc0035b5fc2f83db1beec51d3a4c758ac8de01ed40171303aca333
|
4
|
+
data.tar.gz: f56ffea0e2286170c92ce30a77212786c96d7307f61306e1698362ceb203c3c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f42eda8dd767d1d49abb25ac42b9815c11a5bce20d92b57af8f8dc766ee41dc81fc340a5506844ca02c4017db061d2bfad51335b876bb1cee78fee9f3d0002b3
|
7
|
+
data.tar.gz: 7f7705175f42dc398ce1b2765fa518f848f0a76195c5e20c924d75b67abb0be22e993d6427c0572419aeee5649c4356d03ec02e52852995b3523e5fa1d20beb3
|
data/CODE_OF_CONDUCT.md
CHANGED
@@ -10,21 +10,21 @@ We pledge to act and interact in ways that contribute to an open, welcoming, div
|
|
10
10
|
|
11
11
|
Examples of behavior that contributes to a positive environment for our community include:
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
- Demonstrating empathy and kindness toward other people
|
14
|
+
- Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
- Giving and gracefully accepting constructive feedback
|
16
|
+
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
- Focusing on what is best not just for us as individuals, but for the overall community
|
18
18
|
|
19
19
|
Examples of unacceptable behavior include:
|
20
20
|
|
21
|
-
|
21
|
+
- The use of sexualized language or imagery, and sexual attention or
|
22
22
|
advances of any kind
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
- Public or private harassment
|
25
|
+
- Publishing others' private information, such as a physical or email
|
26
26
|
address, without their explicit permission
|
27
|
-
|
27
|
+
- Other conduct which could reasonably be considered inappropriate in a
|
28
28
|
professional setting
|
29
29
|
|
30
30
|
## Enforcement Responsibilities
|
@@ -39,7 +39,7 @@ This Code of Conduct applies within all community spaces, and also applies when
|
|
39
39
|
|
40
40
|
## Enforcement
|
41
41
|
|
42
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at wc@pico.net.nz. All complaints will be reviewed and investigated promptly and fairly.
|
43
43
|
|
44
44
|
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
45
|
|
@@ -67,7 +67,7 @@ Community leaders will follow these Community Impact Guidelines in determining t
|
|
67
67
|
|
68
68
|
### 4. Permanent Ban
|
69
69
|
|
70
|
-
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior,
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
71
|
|
72
72
|
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
73
|
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
27
27
|
|
28
28
|
$ gem install activerecord-polytypes
|
29
29
|
|
30
|
-
##
|
30
|
+
## Rationale
|
31
31
|
|
32
32
|
Imagine you have a simple Entity model that can be either a User or an Organisation, and it has associated legal documents and a billing plan.
|
33
33
|
|
@@ -71,9 +71,9 @@ What are our options?
|
|
71
71
|
- - We still have to perform aggregations or filters on subtype attributes in memory.
|
72
72
|
- - We leak rigid code-specific strings into our database, making it more difficult to shuffle types.
|
73
73
|
|
74
|
-
#
|
74
|
+
# Use ActiveRecordPolytypes as an alternative mechanism.
|
75
75
|
|
76
|
-
|
76
|
+
With ActiveRecordPolytypes you can easily query across all subtypes like this:
|
77
77
|
|
78
78
|
```ruby
|
79
79
|
Entity::Subtype.where("user_created_at > ? OR organisation_country IN (?)", 1.day.ago, %(US)).order(billing_plan_renewal_date: :desc)
|
@@ -127,6 +127,8 @@ Vs query across all subtypes
|
|
127
127
|
Entity::Subtype.where(...)
|
128
128
|
```
|
129
129
|
|
130
|
+
## Usage
|
131
|
+
|
130
132
|
All you need to do to install ActiveRecordPolytypes into an existing model, is make a call to `polymorphic_supertype_of` in the model, and pass in the names of the associations that you want to act as subtypes.
|
131
133
|
It will work on any existing `belongs_to` or `has_one` association (respecting any non conventional foreign keys, class overrides etc.)
|
132
134
|
|
@@ -156,6 +158,25 @@ end
|
|
156
158
|
Searchable::Subtype.all # => [#<Searchable::User..., #<Searchable::Post.., #<Searchable::Category.., #<Searchable::User..]
|
157
159
|
```
|
158
160
|
|
161
|
+
Subtypes also inherit the relationships of their supertypes, so you can even eager or preload these. E.g.
|
162
|
+
|
163
|
+
On the parent type
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
# Load the user_posts relation for all users. Empty for non users
|
167
|
+
# Load the organisation_documents relation for all organisations. Empty for non organisations
|
168
|
+
assert Entity::Subtype.preload(:user_posts, :organisation_documents).all? { |s| s.user_posts.loaded? && s.organisation_documents.loaded? }
|
169
|
+
assert Entity::Subtype.eager_load(:user_posts, :organisation_documents).all? { |s| s.user_posts.loaded? && s.organisation_documents.loaded? }
|
170
|
+
```
|
171
|
+
|
172
|
+
On the subtype
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
# Load the user_posts relation for all users. Empty for non users
|
176
|
+
assert Entity::User.preload(:posts).all? { |s| s.posts.loaded? }
|
177
|
+
assert Entity::User.eager_load(:posts).all? { |s| s.posts.loaded? }
|
178
|
+
```
|
179
|
+
|
159
180
|
## Development
|
160
181
|
|
161
182
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -159,10 +159,32 @@ module ActiveRecordPolytypes
|
|
159
159
|
associations.each do |association|
|
160
160
|
base_type = association.compute_class(association.class_name)
|
161
161
|
|
162
|
+
base_type.reflect_on_all_associations.each do |assoc|
|
163
|
+
case assoc.macro
|
164
|
+
when :belongs_to
|
165
|
+
subtype_class.belongs_to :"#{association.name}_#{assoc.name}", assoc.scope, **assoc.options, class_name: assoc.class_name
|
166
|
+
when :has_one, :has_many
|
167
|
+
scope = if assoc.options.key?(:as)
|
168
|
+
refined = ->{ where(assoc.type => base_type.name) }
|
169
|
+
if assoc.scope
|
170
|
+
->{ instance_exec(&refined).instance_exec(&assoc.scope) }
|
171
|
+
else
|
172
|
+
refined
|
173
|
+
end
|
174
|
+
else
|
175
|
+
assoc.scope
|
176
|
+
end
|
177
|
+
self.send(assoc.macro, :"#{association.name}_#{assoc.name}", scope, **assoc.options.except(:inverse_of, :destroy, :as), primary_key: "#{association.name}_#{base_type.primary_key}", foreign_key: assoc.foreign_key, class_name: "::#{assoc.class_name}")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
associations.each do |association|
|
183
|
+
base_type = association.compute_class(association.class_name)
|
162
184
|
# Generate a class name for the subtype proxy.
|
163
185
|
subtype_class_name = "#{supertype_type.name}::#{base_type.name}"
|
164
186
|
# Dynamically create a proxy class for the multi-table inheritance.
|
165
|
-
build_mti_proxy_class!(association, base_type, supertype_type)
|
187
|
+
build_mti_proxy_class!(association, base_type, supertype_type, subtype_class)
|
166
188
|
|
167
189
|
select_components_by_type[association.name] = base_type.columns.map do |column|
|
168
190
|
column_name = "#{association.name}_#{column.name}"
|
@@ -186,6 +208,7 @@ module ActiveRecordPolytypes
|
|
186
208
|
join_components_by_type[typename]
|
187
209
|
]
|
188
210
|
end.transpose
|
211
|
+
|
189
212
|
from(<<~SQL)
|
190
213
|
(
|
191
214
|
SELECT #{table_name}.*,#{select_components * ","}, CASE #{case_components * " "} ELSE '#{name}' END AS type
|
@@ -200,12 +223,12 @@ module ActiveRecordPolytypes
|
|
200
223
|
end
|
201
224
|
|
202
225
|
# Dynamically builds a proxy class for a given association to handle multi-table inheritance.
|
203
|
-
def build_mti_proxy_class!(association, base_type, supertype_type)
|
226
|
+
def build_mti_proxy_class!(association, base_type, supertype_type, subtype_class)
|
204
227
|
# Remove any previously defined constant to avoid constant redefinition warnings.
|
205
228
|
supertype_type.send(:remove_const, base_type.name) if supertype_type.constants.include?(base_type.name.to_sym)
|
206
229
|
|
207
230
|
# Define a new class inherited from the current class acting as the subtype.
|
208
|
-
subtype_class = supertype_type.const_set(base_type.name, Class.new(
|
231
|
+
subtype_class = supertype_type.const_set(base_type.name, Class.new(subtype_class))
|
209
232
|
subtype_class.class_eval do
|
210
233
|
attr_reader :inner
|
211
234
|
|
@@ -217,9 +240,27 @@ module ActiveRecordPolytypes
|
|
217
240
|
after_save :reload, if: :previously_new_record?
|
218
241
|
|
219
242
|
# Define attributes and delegation methods for columns inherited from the base type.
|
243
|
+
base_type.reflect_on_all_associations.each do |assoc|
|
244
|
+
case assoc.macro
|
245
|
+
when :belongs_to
|
246
|
+
belongs_to assoc.name, assoc.scope, **assoc.options, class_name: assoc.class_name
|
247
|
+
when :has_one, :has_many
|
248
|
+
scope = if assoc.options.key?(:as)
|
249
|
+
refined = ->{ where(assoc.type => base_type.name) }
|
250
|
+
if assoc.scope
|
251
|
+
->{ instance_exec(&refined).instance_exec(&assoc.scope) }
|
252
|
+
else
|
253
|
+
refined
|
254
|
+
end
|
255
|
+
else
|
256
|
+
assoc.scope
|
257
|
+
end
|
258
|
+
self.send(assoc.macro, assoc.name, scope, **assoc.options.except(:inverse_of, :destroy, :as), primary_key: "#{association.name}_#{base_type.primary_key}", foreign_key: assoc.foreign_key, class_name: "::#{assoc.class_name}")
|
259
|
+
end
|
260
|
+
end
|
220
261
|
base_type.columns.each do |column|
|
221
262
|
column_name = "#{association.name}_#{column.name}"
|
222
|
-
attribute column_name
|
263
|
+
attribute column_name
|
223
264
|
delegate column.name, to: :@inner, allow_nil: true, prefix: association.name
|
224
265
|
define_method :"#{column_name}=" do |value|
|
225
266
|
case
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-polytypes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|