discriminable 2.2.5 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|