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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b497fdb2a24f47cb09e211407bb91fb07fd02c7566702e64ed7d02609d430ec
4
- data.tar.gz: 8dd4464ff4cad4e6235e02bc8dde5eefe3f0ea4f9b0b32e94624e25188aa3f0a
3
+ metadata.gz: 3f3be0fa32d9828b9d37795c4f04974db45b399b1b729701cc3466bc5136ac2a
4
+ data.tar.gz: 5e0039a3a78058800f9ae0924146eba632ad3784457c13b448d98cc174b73466
5
5
  SHA512:
6
- metadata.gz: 87c3f76d275be7dc83ed812fd06fa5f9b302b2709920342a17ec7cdcff24a688be648fe29f7c7b1f426270119f5350ec74f7feb09eca684ecb8f2b30000cefde
7
- data.tar.gz: 8f8367a33ce5bd14c11664bc08dac19d6bc32614f355a69aafd0c7c67a1de164e93f3890ff99f2933e284761ce797adfbf757b66ace722685a6308d364c6e44f
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 classes in your class hierarchy. This makes storing class names in a `type` column redundant.
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
- discriminable_by :state
30
+ discriminable_attribute :state
29
31
  end
30
32
 
31
33
  class Cart < Order
32
- discriminable_as :open
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
- discriminable_by :state
53
+ discriminable_attribute :state
52
54
  end
53
55
 
54
56
  class Cart < Order
55
- discriminable_as :open
57
+ discriminable_value :open
56
58
  end
57
59
 
58
60
  class Invoice < Order
59
- discriminable_as :invoiced
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 `discriminable_by` definition.
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
- discriminable_by :kind
76
+ discriminable_attribute :kind
75
77
  end
76
78
 
77
79
  class NumberProperty < Property
78
- discriminable_as 1
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
- discriminable_as 2, 3, 4
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).
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Discriminable
4
- VERSION = "2.2.5"
4
+ VERSION = "3.0.0"
5
5
  end
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
- # discriminable state: { lead: "Lead" }
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 :discriminable_map, instance_writer: false
28
- class_attribute :discriminable_inverse_map, instance_writer: false
29
- class_attribute :discriminable_values, instance_writer: false
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
- # Specify the column to use for discrimination.
32
+ # This adds
33
+ # - `discriminable_attribute` and
34
+ # - `discriminalbe_value`
35
+ # class methods (plus some aliases).
33
36
  module ClassMethods
34
- def discriminable(**options)
35
- raise "Subclasses should not override .discriminable" unless base_class?
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
- # Use first key as default discriminator
43
- # { a: "C", b: "C" }.invert => { "C" => :b }
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
- def discriminable_by(attribute)
53
- raise "Subclasses should not override .discriminable_by" unless base_class?
54
-
55
- self.discriminable_map ||= discriminable_map_memoized
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
- attribute = attribute.to_s
59
- self.inheritance_column = attribute_aliases[attribute] || attribute
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.discriminable_values = values.map do |value|
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
- discriminable_inverse_map[name]
76
+ _discriminable_inverse_map[name]
73
77
  end
74
78
 
75
79
  def sti_names
76
- ([self] + descendants).flat_map(&:discriminable_values)
80
+ ([self] + descendants).flat_map(&:_discriminable_values)
77
81
  end
78
82
 
79
83
  def type_condition(table = arel_table)
80
- return super unless discriminable_values.present?
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 = discriminable_map[value])
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 = discriminable_value(attrs)
103
+ value = _discriminable_value(attrs)
100
104
  sti_class_for(value)
101
105
  end
102
106
 
103
- def discriminable_map_memoized
107
+ def _discriminable_map_memoized
104
108
  Hash.new do |map, value|
105
- map[value] = descendants.detect { |d| d.discriminable_values.include? value }&.name
109
+ map[value] = descendants.detect { |d| d._discriminable_values.include? value }&.name
106
110
  end
107
111
  end
108
112
 
109
- def discriminable_inverse_map_memoized
113
+ def _discriminable_inverse_map_memoized
110
114
  Hash.new do |map, value|
111
- map[value] = value.constantize.discriminable_values&.first
115
+ map[value] = value.constantize._discriminable_values&.first
112
116
  end
113
117
  end
114
118
 
115
- def discriminable_value(attrs)
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: 2.2.5
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-04-29 00:00:00.000000000 Z
11
+ date: 2022-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord