activerecord-polytypes 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|