discriminable 2.0.0 → 2.2.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: f0d473ffb5de9687b61d3ee960207d15fdc8a3f4d29fbd1a7498740c7cef3e35
4
- data.tar.gz: 5a184093d1cf560fd87284447b7047c4b82fc266a530d9c05e1b9755a8044573
3
+ metadata.gz: 63543cd5b47a63a6870368d2d16c9570e7cd0cebb53cfd296d717884c37fb4c3
4
+ data.tar.gz: b4417f8e33ff2d24a71a9ec436a26b66c38cab59395ed7fbb1182c818ff48439
5
5
  SHA512:
6
- metadata.gz: 347d5b0e409d013e34d64ce6bce73bb8a72e93fb0a872bed81919a4a2aca7c4335ceb6de667fd6a6249a624bb2a8ff2393251107891df5667cfed62c819b386b
7
- data.tar.gz: e716b423ca4bd46bd7b34cbcb2b310773344a6bf572d4b0c376f60e04cbafe0233d7ae0e68fa91ba27e3d9bb7156b0ba477dca205e850049c518ff264299bfe4
6
+ metadata.gz: e053310353bf54fcfbef35b32b5de0015f9b28903cb6dc04631bc04cfbaeec274359c0dc34d687eca7a9813eee36590f2c745e90d3c9587e381fceb9bb38886d
7
+ data.tar.gz: dbf905a1c5a7c7c10154ad5cc9eb73b67d81cd65513e5ac7a1ab33086dbfaee92a885b511942ecc37db4b8528a75283d270ec807db9c4982defb7ba7f2c814a8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.2.0](https://github.com/gregorw/discriminable/compare/v2.1.1...v2.2.0) (2022-04-16)
4
+
5
+
6
+ ### Features
7
+
8
+ * Alternative syntax respecting open-closed principle ([8a8f885](https://github.com/gregorw/discriminable/commit/8a8f8857dc24753dc08f21b2bfbb7b4317b7ed5c))
9
+
10
+ ### [2.1.1](https://github.com/gregorw/discriminable/compare/v2.1.0...v2.1.1) (2022-04-15)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * Subclass is not properly initialized on new objects ([c24706c](https://github.com/gregorw/discriminable/commit/c24706c9e8cd4c80ea20016e341f010a5a3c792b))
16
+ * Use correct, i.e. first value, when using multiple states for same subclass ([3a05c0d](https://github.com/gregorw/discriminable/commit/3a05c0d3a76080a39f40b1d57ce2862316693f97))
17
+
18
+ ## [2.1.0](https://github.com/gregorw/discriminable/compare/v2.0.0...v2.1.0) (2022-04-01)
19
+
20
+
21
+ ### Features
22
+
23
+ * discriminable booleans ([f2ce286](https://github.com/gregorw/discriminable/commit/f2ce2867db9b9f0504474d7237faa9caaab96624))
24
+
3
25
  ## [2.0.0](https://github.com/gregorw/discriminable/compare/v1.0.2...v2.0.0) (2022-03-31)
4
26
 
5
27
 
data/README.md CHANGED
@@ -1,10 +1,20 @@
1
- [![Ruby](https://github.com/gregorw/discriminable/actions/workflows/main.yml/badge.svg)](https://github.com/gregorw/discriminable/actions/workflows/main.yml)
1
+
2
2
 
3
3
  # Discriminable
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/discriminable`. To experiment with that code, run `bin/console` for an interactive prompt.
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)
7
+ [![Maintainability](https://api.codeclimate.com/v1/badges/94041c5f946b64040368/maintainability)](https://codeclimate.com/github/gregorw/discriminable/maintainability)
8
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/94041c5f946b64040368/test_coverage)](https://codeclimate.com/github/gregorw/discriminable/test_coverage)
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).
6
17
 
7
- TODO: Delete this and the text above, and describe your gem
8
18
 
9
19
  ## Installation
10
20
 
@@ -18,13 +28,45 @@ If bundler is not being used to manage dependencies, install the gem by executin
18
28
 
19
29
  ## Usage
20
30
 
21
- TODO: Write usage instructions here
31
+ ```ruby
32
+ class Order < ActiveRecord::Base
33
+ include Discriminable
34
+
35
+ enum state: { open: 0, completed: 1 }
36
+ discriminable state: { open: "Cart" }
37
+ end
38
+
39
+ class Cart < Order
40
+ end
41
+
42
+ Cart.create
43
+ => #<Cart id: 1, state: "open">
44
+ Order.all
45
+ => #<ActiveRecord::Relation [#<Cart id: 1, state: "open">]>
46
+ ```
47
+
48
+ ### Alternative syntax
49
+
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.
51
+
52
+ ```ruby
53
+ class Order < ActiveRecord::Base
54
+ include Discriminable
55
+
56
+ enum state: { open: 0, completed: 1 }
57
+ discriminable_by :state
58
+ end
22
59
 
23
- ## Development
60
+ class Cart < Order
61
+ discriminable_as :open
62
+ end
24
63
 
25
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
64
+ Cart.create
65
+ => #<Cart id: 1, state: "open">
66
+ Order.all
67
+ => #<ActiveRecord::Relation [#<Cart id: 1, state: "open">]>
68
+ ```
26
69
 
27
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
70
 
29
71
  ## Contributing
30
72
 
data/Rakefile CHANGED
@@ -14,3 +14,8 @@ require "rubocop/rake_task"
14
14
  RuboCop::RakeTask.new
15
15
 
16
16
  task default: %i[test rubocop]
17
+
18
+ # Release task
19
+ # gem build *.gemspec
20
+ # gem push *.gem
21
+ # rm *.gem
data/TODO.md CHANGED
@@ -1,2 +1,6 @@
1
+ - [x] multiple values per subclass
2
+ - [x] default to first value when using hash syntax
3
+ - [x] open-closed principle
4
+ - [ ] use `type` column with enum, int, string, etc.
5
+ - [ ] test permitted attributes
1
6
  - [ ] `self.abstract_class = true`
2
- - [ ] …
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Discriminable
4
- VERSION = "2.0.0"
4
+ VERSION = "2.2.0"
5
5
  end
data/lib/discriminable.rb CHANGED
@@ -25,7 +25,9 @@ module Discriminable
25
25
 
26
26
  included do
27
27
  class_attribute :discriminable_map, instance_writer: false
28
- class_attribute :discriminable_inverse, instance_writer: false
28
+ class_attribute :discriminable_inverse_map, instance_writer: false
29
+ class_attribute :discriminable_values, instance_writer: false
30
+ class_attribute :discriminable_as_descendant_value, instance_writer: false
29
31
  end
30
32
 
31
33
  # Specify the column to use for discrimination.
@@ -33,18 +35,49 @@ module Discriminable
33
35
  def discriminable(**options)
34
36
  raise "Subclasses should not override .discriminable" unless base_class?
35
37
 
36
- discriminator, map = options.first
38
+ attribute, map = options.first
37
39
 
38
40
  self.discriminable_map = map.with_indifferent_access
39
- self.discriminable_inverse = map.invert
40
- self.inheritance_column = discriminator.to_s
41
+
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
+ self.discriminable_inverse_map = map.to_a.reverse.to_h.invert
46
+ self.inheritance_column = attribute.to_s
47
+ end
48
+
49
+ def discriminable_by(attribute)
50
+ raise "Subclasses should not override .discriminable_by" unless base_class?
51
+
52
+ self.discriminable_as_descendant_value = true
53
+ self.inheritance_column = attribute.to_s
54
+ end
55
+
56
+ def discriminable_as(*values)
57
+ raise "Only subclasses should specify .discriminable_as" if base_class?
58
+
59
+ self.discriminable_values = values.map do |value|
60
+ value.instance_of?(Symbol) ? value.to_s : value
61
+ end
41
62
  end
42
63
 
43
64
  def sti_name
44
- discriminable_inverse[name]
65
+ if discriminable_as_descendant_value
66
+ self.discriminable_inverse_map ||= Hash.new do |map, value|
67
+ map[value] = name.constantize.discriminable_values&.first
68
+ end
69
+ end
70
+
71
+ discriminable_inverse_map[name]
45
72
  end
46
73
 
47
74
  def sti_class_for(value)
75
+ if discriminable_as_descendant_value
76
+ self.discriminable_map ||= Hash.new do |map, v|
77
+ map[v] = descendants.detect { |d| d.discriminable_values.include? v }&.name
78
+ end
79
+ end
80
+
48
81
  return self unless (type_name = discriminable_map[value])
49
82
 
50
83
  super type_name
@@ -57,7 +90,8 @@ module Discriminable
57
90
  attrs = attrs.to_h if attrs.respond_to?(:permitted?)
58
91
  return unless attrs.is_a?(Hash)
59
92
 
60
- value = base_class.type_for_attribute(inheritance_column).cast(attrs[inheritance_column])
93
+ value = attrs.with_indifferent_access[inheritance_column]
94
+ value = base_class.type_for_attribute(inheritance_column).cast(value)
61
95
  sti_class_for(value)
62
96
  end
63
97
  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.0.0
4
+ version: 2.2.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-03-31 00:00:00.000000000 Z
11
+ date: 2022-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.26'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.17.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.17.0
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: sqlite3
85
99
  requirement: !ruby/object:Gem::Requirement