discriminable 2.2.5 → 3.0.0
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/CHANGELOG.md +17 -0
- data/README.md +37 -10
- data/lib/discriminable/version.rb +1 -1
- data/lib/discriminable.rb +39 -35
- 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: 3f3be0fa32d9828b9d37795c4f04974db45b399b1b729701cc3466bc5136ac2a
|
4
|
+
data.tar.gz: 5e0039a3a78058800f9ae0924146eba632ad3784457c13b448d98cc174b73466
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea6a3450967d34c3e275ba4da051463bd1ca9d5a2f79d483edaad6bb5b284bb76bcc498b1ad54b87df62f79728bf1665112a7409c1b621716b813d02201b8c54
|
7
|
+
data.tar.gz: 9cfbe503697d09f795ff4477031ed3afd66e67fc53f01c2a7a226ec6b335eb16ea655fe821ffc4a828afab1c8f2964b16674c47db37491a9619e6a4f3be6720f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [3.0.0](https://github.com/gregorw/discriminable/compare/v2.2.5...v3.0.0) (2022-08-21)
|
4
|
+
|
5
|
+
|
6
|
+
### ⚠ BREAKING CHANGES
|
7
|
+
|
8
|
+
* get rid of legacy hash notation
|
9
|
+
|
10
|
+
### Features
|
11
|
+
|
12
|
+
* Alternate syntax using attribute/value instead of prepositions ([d0843e4](https://github.com/gregorw/discriminable/commit/d0843e4fdf7d9721c29d7259f5c8f0746d345d9b))
|
13
|
+
|
14
|
+
|
15
|
+
### Bug Fixes
|
16
|
+
|
17
|
+
* coverage ([bd862a8](https://github.com/gregorw/discriminable/commit/bd862a835fc0239f0ac59cac1e051293b4b547bc))
|
18
|
+
* get rid of legacy hash notation ([4feda16](https://github.com/gregorw/discriminable/commit/4feda16be37df14dd159b321d3e45a16127fa922))
|
19
|
+
|
3
20
|
### [2.2.5](https://github.com/gregorw/discriminable/compare/v2.2.4...v2.2.5) (2022-04-29)
|
4
21
|
|
5
22
|
|
data/README.md
CHANGED
@@ -9,7 +9,9 @@
|
|
9
9
|
|
10
10
|
This is a Ruby gem that implements single-table inheritance (STI) for ActiveRecord models using string, integer and boolean column types.
|
11
11
|
|
12
|
-
In other words, use any (existing) model attribute to discriminate between different
|
12
|
+
In other words, it allows to use any (existing) model attribute to discriminate between different subclasses in your class hierarchy. This makes storing class names in a `type` column redundant.
|
13
|
+
|
14
|
+
Also, it supports aliased attributes and _multiple_ values per subclass.
|
13
15
|
|
14
16
|
## Installation
|
15
17
|
|
@@ -25,11 +27,11 @@ or
|
|
25
27
|
class Order < ActiveRecord::Base
|
26
28
|
include Discriminable
|
27
29
|
|
28
|
-
|
30
|
+
discriminable_attribute :state
|
29
31
|
end
|
30
32
|
|
31
33
|
class Cart < Order
|
32
|
-
|
34
|
+
discriminable_value :open
|
33
35
|
end
|
34
36
|
|
35
37
|
Cart.create
|
@@ -48,21 +50,21 @@ class Order < ActiveRecord::Base
|
|
48
50
|
|
49
51
|
enum state: { open: 0, processing: 1, invoiced: 2 }
|
50
52
|
|
51
|
-
|
53
|
+
discriminable_attribute :state
|
52
54
|
end
|
53
55
|
|
54
56
|
class Cart < Order
|
55
|
-
|
57
|
+
discriminable_value :open
|
56
58
|
end
|
57
59
|
|
58
60
|
class Invoice < Order
|
59
|
-
|
61
|
+
discriminable_value :invoiced
|
60
62
|
end
|
61
63
|
```
|
62
64
|
|
63
65
|
### Aliased attributes
|
64
66
|
|
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 `
|
67
|
+
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_attribute` definition.
|
66
68
|
|
67
69
|
```ruby
|
68
70
|
class Property < ActiveRecord::Base
|
@@ -71,11 +73,11 @@ class Property < ActiveRecord::Base
|
|
71
73
|
alias_attribute :kind, :kind_with_legacy_postfix
|
72
74
|
|
73
75
|
# Aliased attributes are supported when specifying the discriminable attribute
|
74
|
-
|
76
|
+
discriminable_attribute :kind
|
75
77
|
end
|
76
78
|
|
77
79
|
class NumberProperty < Property
|
78
|
-
|
80
|
+
discriminable_value 1
|
79
81
|
end
|
80
82
|
```
|
81
83
|
|
@@ -86,12 +88,37 @@ Sometimes, in a real project, you may want to map a number of values to a single
|
|
86
88
|
```ruby
|
87
89
|
class OptionProperty < Property
|
88
90
|
# The first mention becomes the default value
|
89
|
-
|
91
|
+
discriminable_values 2, 3, 4
|
90
92
|
end
|
91
93
|
```
|
92
94
|
|
93
95
|
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.
|
94
96
|
|
97
|
+
|
98
|
+
## Comparison with standard Rails
|
99
|
+
|
100
|
+
|
101
|
+
### Rails STI
|
102
|
+
|
103
|
+
| *values* | string | integer | boolean | enum | decimal | … |
|
104
|
+
|--|--|--|--|--|--|--|
|
105
|
+
| single | 🟡 `class.name` only | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 |
|
106
|
+
| multiple | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 | 🔴 |
|
107
|
+
|
108
|
+
### Discriminable Gem
|
109
|
+
|
110
|
+
| *values* | string | integer | boolean | enum | decimal | … |
|
111
|
+
|--|--|--|--|--|--| --|
|
112
|
+
| single | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
|
113
|
+
| multiple | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 |
|
114
|
+
|
115
|
+
“Multiple” means that more than one value can map to a single subclass. This may or may not be useful for your use case. In standard Rails, the a single class name obviously maps to a single class.
|
116
|
+
|
117
|
+
## Prerequisits
|
118
|
+
|
119
|
+
Rails 5+ is required.
|
120
|
+
|
121
|
+
|
95
122
|
## Contributing
|
96
123
|
|
97
124
|
Bug reports and pull requests are welcome on GitHub at https://github.com/gregorw/discriminable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/gregorw/discriminable/blob/main/CODE_OF_CONDUCT.md).
|
data/lib/discriminable.rb
CHANGED
@@ -17,74 +17,78 @@ require "active_support"
|
|
17
17
|
# class Customer < ActiveRecord::Base
|
18
18
|
# include Discriminable
|
19
19
|
#
|
20
|
-
#
|
20
|
+
# discriminable_by :state
|
21
21
|
# end
|
22
22
|
#
|
23
23
|
module Discriminable
|
24
24
|
extend ActiveSupport::Concern
|
25
25
|
|
26
26
|
included do
|
27
|
-
class_attribute :
|
28
|
-
class_attribute :
|
29
|
-
class_attribute :
|
27
|
+
class_attribute :_discriminable_map, instance_writer: false
|
28
|
+
class_attribute :_discriminable_inverse_map, instance_writer: false
|
29
|
+
class_attribute :_discriminable_values, instance_writer: false
|
30
30
|
end
|
31
31
|
|
32
|
-
#
|
32
|
+
# This adds
|
33
|
+
# - `discriminable_attribute` and
|
34
|
+
# - `discriminalbe_value`
|
35
|
+
# class methods (plus some aliases).
|
33
36
|
module ClassMethods
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
attribute, map = options.first
|
38
|
-
|
39
|
-
# E.g. { value: "ClassName" }
|
40
|
-
self.discriminable_map = map.with_indifferent_access
|
37
|
+
# Specify the attribute/column at the root class to use for discrimination.
|
38
|
+
def discriminable_by(attribute)
|
39
|
+
raise "Subclasses should not override .discriminable_by" unless base_class?
|
41
40
|
|
42
|
-
|
43
|
-
|
44
|
-
# { a: "C", b: "C" }.to_a.reverse.to_h.invert => { "C" => :a }
|
45
|
-
# E.g. { "ClassName" => :value }
|
46
|
-
self.discriminable_inverse_map = map.to_a.reverse.to_h.invert
|
41
|
+
self._discriminable_map ||= _discriminable_map_memoized
|
42
|
+
self._discriminable_inverse_map ||= _discriminable_inverse_map_memoized
|
47
43
|
|
48
44
|
attribute = attribute.to_s
|
49
45
|
self.inheritance_column = attribute_aliases[attribute] || attribute
|
50
46
|
end
|
51
47
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
self.discriminable_inverse_map ||= discriminable_inverse_map_memoized
|
48
|
+
# “Aliases” for discriminable_by
|
49
|
+
def discriminable_attribute(attribute)
|
50
|
+
discriminable_by(attribute)
|
51
|
+
end
|
57
52
|
|
58
|
-
|
59
|
-
|
53
|
+
def discriminable_on(attribute)
|
54
|
+
discriminable_by(attribute)
|
60
55
|
end
|
61
56
|
|
57
|
+
# Specify the values the subclass corresponds to.
|
62
58
|
def discriminable_as(*values)
|
63
59
|
raise "Only subclasses should specify .discriminable_as" if base_class?
|
64
60
|
|
65
|
-
self.
|
61
|
+
self._discriminable_values = values.map do |value|
|
66
62
|
value.instance_of?(Symbol) ? value.to_s : value
|
67
63
|
end
|
68
64
|
end
|
69
65
|
|
66
|
+
def discriminable_value(*values)
|
67
|
+
discriminable_as(*values)
|
68
|
+
end
|
69
|
+
|
70
|
+
def discriminable_values(*values)
|
71
|
+
discriminable_as(*values)
|
72
|
+
end
|
73
|
+
|
70
74
|
# This is the value of the discriminable attribute
|
71
75
|
def sti_name
|
72
|
-
|
76
|
+
_discriminable_inverse_map[name]
|
73
77
|
end
|
74
78
|
|
75
79
|
def sti_names
|
76
|
-
([self] + descendants).flat_map(&:
|
80
|
+
([self] + descendants).flat_map(&:_discriminable_values)
|
77
81
|
end
|
78
82
|
|
79
83
|
def type_condition(table = arel_table)
|
80
|
-
return super unless
|
84
|
+
return super unless _discriminable_values.present?
|
81
85
|
|
82
86
|
sti_column = table[inheritance_column]
|
83
87
|
predicate_builder.build(sti_column, sti_names)
|
84
88
|
end
|
85
89
|
|
86
90
|
def sti_class_for(value)
|
87
|
-
return self unless (type_name =
|
91
|
+
return self unless (type_name = _discriminable_map[value])
|
88
92
|
|
89
93
|
super type_name
|
90
94
|
end
|
@@ -96,23 +100,23 @@ module Discriminable
|
|
96
100
|
attrs = attrs.to_h if attrs.respond_to?(:permitted?)
|
97
101
|
return unless attrs.is_a?(Hash)
|
98
102
|
|
99
|
-
value =
|
103
|
+
value = _discriminable_value(attrs)
|
100
104
|
sti_class_for(value)
|
101
105
|
end
|
102
106
|
|
103
|
-
def
|
107
|
+
def _discriminable_map_memoized
|
104
108
|
Hash.new do |map, value|
|
105
|
-
map[value] = descendants.detect { |d| d.
|
109
|
+
map[value] = descendants.detect { |d| d._discriminable_values.include? value }&.name
|
106
110
|
end
|
107
111
|
end
|
108
112
|
|
109
|
-
def
|
113
|
+
def _discriminable_inverse_map_memoized
|
110
114
|
Hash.new do |map, value|
|
111
|
-
map[value] = value.constantize.
|
115
|
+
map[value] = value.constantize._discriminable_values&.first
|
112
116
|
end
|
113
117
|
end
|
114
118
|
|
115
|
-
def
|
119
|
+
def _discriminable_value(attrs)
|
116
120
|
attrs = attrs.with_indifferent_access
|
117
121
|
value = attrs[inheritance_column]
|
118
122
|
value ||= attrs[attribute_aliases.invert[inheritance_column]]
|
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:
|
4
|
+
version: 3.0.0
|
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-
|
11
|
+
date: 2022-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|