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 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