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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de50052f2a84d18714886f41876bfe80b9d9b9b4e7fd426338c4029fad5b925b
4
- data.tar.gz: d94bdc1f419a3f1aed49965f8c09fe36fabf833ffaf8dae2837a38d2cc760d2b
3
+ metadata.gz: 14bd860961bc0035b5fc2f83db1beec51d3a4c758ac8de01ed40171303aca333
4
+ data.tar.gz: f56ffea0e2286170c92ce30a77212786c96d7307f61306e1698362ceb203c3c7
5
5
  SHA512:
6
- metadata.gz: e3c518408bf522adf86567c0f4a6f9bc0696bcf385b89159c29678a51de2d9660494c4a69ace25049920a67bac957fee425396b53526df71bfe4458f36ada2d0
7
- data.tar.gz: ce13ce16d5239f575a98b2acd29cdd50d69bf36001e481cef893a76fb96f01ba27b2e790095d0d2a2c4c6e991b19e1dc8dd59e0be599856bac6381f98ade7633
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
- * 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
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
- * The use of sexualized language or imagery, and sexual attention or
21
+ - The use of sexualized language or imagery, and sexual attention or
22
22
  advances of any kind
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
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
- * Other conduct which could reasonably be considered inappropriate in a
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 wouter.coppieters@employmenthero.com. All complaints will be reviewed and investigated promptly and fairly.
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, harassment of an individual, or aggression toward or disparagement of classes of individuals.
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
- ## Usage
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
- # This is where ActiveRecordPolytypes provides an alternative mechanism.
74
+ # Use ActiveRecordPolytypes as an alternative mechanism.
75
75
 
76
- # With ActiveRecordPolytypes you can easily query across all subtypes like this:
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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordPolytypes
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
@@ -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(self))
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, column.type
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.0
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-15 00:00:00.000000000 Z
11
+ date: 2024-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord