discriminable 2.2.1 → 2.2.4

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: 332984f03395d4725f162027692324d66fa591f19820d7690201677c37ab8d0c
4
- data.tar.gz: 49017319a8f0cc0ff6bbf498ec32fc68fc51cf062821d683e58b1340b317e7f3
3
+ metadata.gz: 91332d7c33b8d092f2071dd5d4a59b12786058d8347ba844a8426354133f4e2c
4
+ data.tar.gz: b743b5f2db1ea1690a5bad69b6b046a58dd14e76c64e47de03dd18a8b57b8003
5
5
  SHA512:
6
- metadata.gz: 23b33e6a0ebe2009ec62ef57747fbb585cd400837476a3199f28ff8733f607f3974b667713fd788457824adaa9a09ee93e0093e071010016d389b2eab2ec415a
7
- data.tar.gz: fabeb7320aa926567261756a3d2c9863feb193abfd4124da42a47588d84ab3b6a4736bd197d850aa74fbd8c6841c7077bbeb41020549f0e962ca54f0d132e494
6
+ metadata.gz: f0a230f1257e8a53e914c8ac39b879143a897ca0d909441403494afa2e0ba2a48ea529b58efb85b8946c97416dc407ac7bf5cda60a12482cec0aceff22cc6169
7
+ data.tar.gz: abcc79722fafc181c21f1e27220c4ce46de790e90499de8dd97c94f568b1143d094d6498c93d5975836ec3781fba5e38d8ba51e8b8f002ba1e8dfb4d1d20bb25
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ### [2.2.4](https://github.com/gregorw/discriminable/compare/v2.2.3...v2.2.4) (2022-04-25)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * changelog url ([3054a1a](https://github.com/gregorw/discriminable/commit/3054a1a0137d8d3cccb55d7f2f06cb392053dab0))
9
+ * support querying when multiple values are provided ([f75190b](https://github.com/gregorw/discriminable/commit/f75190bcf33dcbddf507626bc77aa4709a594f4d))
10
+
11
+ ### [2.2.3](https://github.com/gregorw/discriminable/compare/v2.2.2...v2.2.3) (2022-04-20)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * Allow to use attribute aliases ([448e4e8](https://github.com/gregorw/discriminable/commit/448e4e81925543f1ee4949898f0ac6628eb2d3fd))
17
+
18
+ ### [2.2.2](https://github.com/gregorw/discriminable/compare/v2.2.1...v2.2.2) (2022-04-17)
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * Typo in gemspec uris ([f77253f](https://github.com/gregorw/discriminable/commit/f77253f2cb2f400e04baf3c7bd6083f42ae3aa6b))
24
+
3
25
  ### [2.2.1](https://github.com/gregorw/discriminable/compare/v2.2.0...v2.2.1) (2022-04-17)
4
26
 
5
27
 
data/README.md CHANGED
@@ -3,28 +3,21 @@
3
3
  # Discriminable
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/discriminable.svg)](https://rubygems.org/gems/discriminable)
6
- [![CI](https://github.com/gregorw/discriminable/actions/workflows/main.yml/badge.svg)](https://github.com/gregorw/discriminable/actions/workflows/main.yml)
6
+ [![CI](https://github.com/gregorw/discriminable/actions/workflows/main.yml/badge.svg?event=push)](https://github.com/gregorw/discriminable/actions/workflows/main.yml)
7
7
  [![Maintainability](https://api.codeclimate.com/v1/badges/94041c5f946b64040368/maintainability)](https://codeclimate.com/github/gregorw/discriminable/maintainability)
8
8
  [![Test Coverage](https://api.codeclimate.com/v1/badges/94041c5f946b64040368/test_coverage)](https://codeclimate.com/github/gregorw/discriminable/test_coverage)
9
9
 
10
- Single table inheritance (STI) for Ruby on Rails models (ActiveRecord) using enum, boolean, string and integer column types.
11
-
12
- In other words, use any _existing_ model attribute for STI instead of storing class names in a `type` column.
13
-
14
- **Related work**
15
-
16
- The idea was originally described in [“Bye Bye STI, Hello Discriminable Model”](https://www.salsify.com/blog/engineering/bye-bye-sti-hello-discriminable-model) by Randy Burkes and this Gem has started out with [his code](https://gist.github.com/rlburkes/798e186acb2f93e787a5).
10
+ This is a Ruby gem that implements single-table inheritance (STI) for ActiveRecord models using string, integer and boolean column types.
17
11
 
12
+ In other words, use any (existing) model attribute to discriminate between different classes in your class hierarchy. This makes storing class names in a `type` column redundant.
18
13
 
19
14
  ## Installation
20
15
 
21
- Install the gem and add to the application's Gemfile by executing:
22
-
23
- $ bundle add discriminable
16
+ bundle add discriminable
24
17
 
25
- If bundler is not being used to manage dependencies, install the gem by executing:
18
+ or
26
19
 
27
- $ gem install discriminable
20
+ gem install discriminable
28
21
 
29
22
  ## Usage
30
23
 
@@ -32,28 +25,29 @@ If bundler is not being used to manage dependencies, install the gem by executin
32
25
  class Order < ActiveRecord::Base
33
26
  include Discriminable
34
27
 
35
- enum state: { open: 0, completed: 1 }
36
- discriminable state: { open: "Cart" }
28
+ discriminable_by :state
37
29
  end
38
30
 
39
31
  class Cart < Order
32
+ discriminable_as :open
40
33
  end
41
34
 
42
35
  Cart.create
43
- => #<Cart id: 1, state: "open">
36
+ # => #<Cart id: 1, state: "open">
44
37
  Order.all
45
- => #<ActiveRecord::Relation [#<Cart id: 1, state: "open">]>
38
+ # => #<ActiveRecord::Relation [#<Cart id: 1, state: "open">]>
46
39
  ```
47
40
 
48
- ### Alternative syntax
41
+ ## Features
49
42
 
50
- Instead of defining subclass names within the parent you may prefer to be open for extension but closed for modification (open-closed principle) using the alternative syntax.
43
+ ### Compatible with enums
51
44
 
52
45
  ```ruby
53
46
  class Order < ActiveRecord::Base
54
47
  include Discriminable
55
48
 
56
- enum state: { open: 0, completed: 1 }
49
+ enum state: { open: 0, processing: 1, invoiced: 2 }
50
+
57
51
  discriminable_by :state
58
52
  end
59
53
 
@@ -61,12 +55,42 @@ class Cart < Order
61
55
  discriminable_as :open
62
56
  end
63
57
 
64
- Cart.create
65
- => #<Cart id: 1, state: "open">
66
- Order.all
67
- => #<ActiveRecord::Relation [#<Cart id: 1, state: "open">]>
58
+ class Invoice < Order
59
+ discriminable_as :invoiced
60
+ end
68
61
  ```
69
62
 
63
+ ### Aliased attributes
64
+
65
+ In case you are working with a legacy database and cannot change the column name easily it’s easy to reference an aliased attribute in the `discriminable_by` definition.
66
+
67
+ ```ruby
68
+ class Property < ActiveRecord::Base
69
+ include Discriminable
70
+
71
+ alias_attribute :kind, :kind_with_legacy_postfix
72
+
73
+ # Aliased attributes are supported when specifying the discriminable attribute
74
+ discriminable_by :kind
75
+ end
76
+
77
+ class NumberProperty < Property
78
+ discriminable_as 1
79
+ end
80
+ ```
81
+
82
+ ### Multiple values
83
+
84
+ Sometimes, in a real project, you may want to map a number of values to a single class. This is possible by specifying:
85
+
86
+ ```ruby
87
+ class OptionProperty < Property
88
+ # The first mention becomes the default value
89
+ discriminable_as 2, 3, 4
90
+ end
91
+ ```
92
+
93
+ Note that when creating new records with e.g. `OptionProperty.create` a _default_ value needs to be set in the database for this discriminable class. The Discriminable gem uses the _first_ value in the list as the default.
70
94
 
71
95
  ## Contributing
72
96
 
@@ -79,3 +103,14 @@ The gem is available as open source under the terms of the [MIT License](https:/
79
103
  ## Code of Conduct
80
104
 
81
105
  Everyone interacting in the Discriminable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/gregorw/discriminable/blob/main/CODE_OF_CONDUCT.md).
106
+
107
+ ## Related work
108
+
109
+ The idea for this Gem was influenced by [“Bye Bye STI, Hello Discriminable Model”](https://www.salsify.com/blog/engineering/bye-bye-sti-hello-discriminable-model) by Randy Burkes. This Gem has started out with [his code](https://gist.github.com/rlburkes/798e186acb2f93e787a5).
110
+
111
+ See also:
112
+
113
+ - Rails [single table inheritance](https://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html) and [DelegatedType](https://api.rubyonrails.org/classes/ActiveRecord/DelegatedType.html)
114
+ - Java [JPA discrimanator](https://openjpa.apache.org/builds/1.0.2/apache-openjpa-1.0.2/docs/manual/jpa_overview_mapping_discrim.html)
115
+ - Python [model inheritance](https://docs.djangoproject.com/en/4.0/topics/db/models/#model-inheritance-1)
116
+ - [Discriminator](https://github.com/gdpelican/discriminator) gem.
data/TODO.md CHANGED
@@ -1,6 +1,12 @@
1
1
  - [x] multiple values per subclass
2
2
  - [x] default to first value when using hash syntax
3
3
  - [x] open-closed principle
4
- - [ ] use `type` column with enum, int, string, etc.
4
+ - [x] Bug: multiple values: Child.all query (double-check)
5
+ - [ ] Rails 5 support (see rails-5 branch)
6
+ - [ ] rubocop-minitest
7
+ - [ ] more tests / examples
8
+ - [ ] Documentation
5
9
  - [ ] test permitted attributes
6
- - [ ] `self.abstract_class = true`
10
+ - [ ] scoping… should work OOTB
11
+ - [ ] ~~`self.abstract_class = true` ➔ This results in separate tables~~
12
+ - [ ] ~~use `type` column with enum, int, string, etc.~~
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Discriminable
4
- VERSION = "2.2.1"
4
+ VERSION = "2.2.4"
5
5
  end
data/lib/discriminable.rb CHANGED
@@ -36,28 +36,28 @@ module Discriminable
36
36
 
37
37
  attribute, map = options.first
38
38
 
39
+ # E.g. { value: "ClassName" }
39
40
  self.discriminable_map = map.with_indifferent_access
40
41
 
41
42
  # Use first key as default discriminator
42
43
  # { a: "C", b: "C" }.invert => { "C" => :b }
43
44
  # { a: "C", b: "C" }.to_a.reverse.to_h.invert => { "C" => :a }
45
+ # E.g. { "ClassName" => :value }
44
46
  self.discriminable_inverse_map = map.to_a.reverse.to_h.invert
45
- self.inheritance_column = attribute.to_s
47
+
48
+ attribute = attribute.to_s
49
+ self.inheritance_column = attribute_aliases[attribute] || attribute
46
50
  end
47
51
 
48
- # rubocop:disable Metrics/AbcSize
49
52
  def discriminable_by(attribute)
50
53
  raise "Subclasses should not override .discriminable_by" unless base_class?
51
54
 
52
- self.inheritance_column = attribute.to_s
53
- self.discriminable_map ||= Hash.new do |map, value|
54
- map[value] = discriminable_descendants(value)&.name
55
- end
56
- self.discriminable_inverse_map ||= Hash.new do |map, value|
57
- map[value] = value.constantize.discriminable_values&.first
58
- end
55
+ self.discriminable_map ||= discriminable_map_memoized
56
+ self.discriminable_inverse_map ||= discriminable_inverse_map_memoized
57
+
58
+ attribute = attribute.to_s
59
+ self.inheritance_column = attribute_aliases[attribute] || attribute
59
60
  end
60
- # rubocop:enable Metrics/AbcSize
61
61
 
62
62
  def discriminable_as(*values)
63
63
  raise "Only subclasses should specify .discriminable_as" if base_class?
@@ -67,10 +67,22 @@ module Discriminable
67
67
  end
68
68
  end
69
69
 
70
+ # This is the value of the discriminable attribute
70
71
  def sti_name
71
72
  discriminable_inverse_map[name]
72
73
  end
73
74
 
75
+ def sti_names
76
+ ([self] + descendants).flat_map(&:discriminable_values)
77
+ end
78
+
79
+ def type_condition(table = arel_table)
80
+ return super unless discriminable_values.present?
81
+
82
+ sti_column = table[inheritance_column]
83
+ predicate_builder.build(sti_column, sti_names)
84
+ end
85
+
74
86
  def sti_class_for(value)
75
87
  return self unless (type_name = discriminable_map[value])
76
88
 
@@ -84,13 +96,27 @@ module Discriminable
84
96
  attrs = attrs.to_h if attrs.respond_to?(:permitted?)
85
97
  return unless attrs.is_a?(Hash)
86
98
 
87
- value = attrs.with_indifferent_access[inheritance_column]
88
- value = base_class.type_for_attribute(inheritance_column).cast(value)
99
+ value = discriminable_value(attrs)
89
100
  sti_class_for(value)
90
101
  end
91
102
 
92
- def discriminable_descendants(value)
93
- descendants.detect { |d| d.discriminable_values.include? value }
103
+ def discriminable_map_memoized
104
+ Hash.new do |map, value|
105
+ map[value] = descendants.detect { |d| d.discriminable_values.include? value }&.name
106
+ end
107
+ end
108
+
109
+ def discriminable_inverse_map_memoized
110
+ Hash.new do |map, value|
111
+ map[value] = value.constantize.discriminable_values&.first
112
+ end
113
+ end
114
+
115
+ def discriminable_value(attrs)
116
+ attrs = attrs.with_indifferent_access
117
+ value = attrs[inheritance_column]
118
+ value ||= attrs[attribute_aliases.invert[inheritance_column]]
119
+ base_class.type_for_attribute(inheritance_column).cast(value)
94
120
  end
95
121
  end
96
122
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discriminable
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregor Wassmann
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-17 00:00:00.000000000 Z
11
+ date: 2022-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -131,13 +131,13 @@ files:
131
131
  - lib/discriminable.rb
132
132
  - lib/discriminable/version.rb
133
133
  - sig/discriminable.rbs
134
- homepage: https://github.com/gregorw/discrimainable
134
+ homepage: https://github.com/gregorw/discriminable
135
135
  licenses:
136
136
  - MIT
137
137
  metadata:
138
- homepage_uri: https://github.com/gregorw/discrimainable
139
- source_code_uri: https://github.com/gregorw/discrimainable
140
- changelog_uri: https://github.com/gregorw/discrimainable/CHANGELOG.md
138
+ homepage_uri: https://github.com/gregorw/discriminable
139
+ source_code_uri: https://github.com/gregorw/discriminable
140
+ changelog_uri: https://github.com/gregorw/discriminable/blob/main/CHANGELOG.md
141
141
  rubygems_mfa_required: 'false'
142
142
  post_install_message:
143
143
  rdoc_options: []