acts_as_recursive_tree 3.4.0 → 3.5.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 +4 -0
- data/README.md +19 -2
- data/lib/acts_as_recursive_tree/acts_macro.rb +3 -2
- data/lib/acts_as_recursive_tree/associations.rb +2 -1
- data/lib/acts_as_recursive_tree/builders/ancestors.rb +1 -1
- data/lib/acts_as_recursive_tree/builders/leaves.rb +1 -1
- data/lib/acts_as_recursive_tree/builders/relation_builder.rb +38 -24
- data/lib/acts_as_recursive_tree/config.rb +13 -2
- data/lib/acts_as_recursive_tree/options/query_options.rb +6 -0
- data/lib/acts_as_recursive_tree/version.rb +1 -1
- data/spec/acts_as_recursive_tree/builders/ancestors_spec.rb +4 -4
- data/spec/acts_as_recursive_tree/builders/descendants_spec.rb +4 -4
- data/spec/acts_as_recursive_tree/builders/leaves_spec.rb +4 -4
- data/spec/acts_as_recursive_tree/options/values_spec.rb +6 -6
- data/spec/support/shared_examples/builders.rb +20 -18
- data/spec/support/tree_methods.rb +2 -6
- 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: 2ce9f3e8c4b5dd8d5e61e04d9234a07920f2063955d134110a40c217950595ea
|
4
|
+
data.tar.gz: baa87fc513a6fd8e49538309b1833e418328b1f7317584fc828d20689cbbd32b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e21465a62a28f6ca757402689abe0709e131d3104f3334646105af34d594a0704d2aaf54fb72b11cb039fd7436845bec0dd335506ed8f5c48c599f89ae9a502
|
7
|
+
data.tar.gz: 242a7f33a0595155d0fc75eec0f17769ae0d92314db07a6cb5acab96737574498266efb6f2aa8f30af3ef3c9a4b87895cb7ab0ebf75d3d9469173131917a32e1
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
### Version 3.5.0
|
2
|
+
- Added :dependent option for setting explicit deletion behaviour (issue #31)
|
3
|
+
- Added automatic cycle detection when supported (currently only PostgresSQL 14+) (issue #22)
|
4
|
+
|
1
5
|
### Version 3.4.0
|
2
6
|
- Rails 7.1 compatibility
|
3
7
|
- Added ar_next to test matrix
|
data/README.md
CHANGED
@@ -55,18 +55,26 @@ class Node < ActiveRecord::Base
|
|
55
55
|
recursive_tree
|
56
56
|
end
|
57
57
|
```
|
58
|
-
That's it. This will assume that your model has a column named `parent_id` which will be used for traversal. If your column is something different, then you can
|
58
|
+
That's it. This will assume that your model has a column named `parent_id` which will be used for traversal. If your column is something different, then you can specify it in the call to `recursive_tree`:
|
59
59
|
|
60
60
|
```ruby
|
61
61
|
recursive_tree parent_key: :some_other_column
|
62
62
|
```
|
63
63
|
|
64
|
-
Some extra special stuff - if your parent relation is also polymorphic,
|
64
|
+
Some extra special stuff - if your parent relation is also polymorphic, then specify the polymorphic column:
|
65
65
|
|
66
66
|
```ruby
|
67
67
|
recursive_tree parent_type_column: :some_other_type_column
|
68
68
|
```
|
69
69
|
|
70
|
+
Controlling deletion behaviour:
|
71
|
+
|
72
|
+
By default, it is up to the user code to delete all child nodes in a tree when a parent node gets deleted. This can be controlled by the `:dependent` option, which will be set on the `children` association (see [#has_many](https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many) in the Rails doc).
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
recursive_tree dependent: :nullify # or :destroy, etc.
|
76
|
+
```
|
77
|
+
|
70
78
|
## Usage
|
71
79
|
|
72
80
|
After you set up a model for usage, there are now several methods you can use.
|
@@ -216,6 +224,15 @@ Instance Methods make no difference of the class from which they are called:
|
|
216
224
|
sub_node_instance.descendants # => returns Node and SubNode instances
|
217
225
|
```
|
218
226
|
|
227
|
+
## A note on endless recursion / cycle detection
|
228
|
+
|
229
|
+
### Inserting
|
230
|
+
As of now it is up to the user code to guarantee there will be no cycles created in the parent/child entries. If not, your DB might run into an endless recursion. Inserting/updating records that will cause a cycle is not prevented by some validation checks, so you have to do this by your own. This might change in a future version.
|
231
|
+
|
232
|
+
### Querying
|
233
|
+
If you want to make sure to not run into an endless recursion when querying, then there are following options:
|
234
|
+
1. Add a maximum depth to the query options. If an cycle is present in your data, the recursion will stop when reaching the max depth and stop further traversing.
|
235
|
+
2. When you are on recent version of PostgreSQL (14+) you are lucky. Postgres added the CYCLE detection feature to detect cycles and prevent endless recursion. Our query builder will add this feature if your DB does support this.
|
219
236
|
|
220
237
|
## Contributing
|
221
238
|
|
@@ -7,13 +7,14 @@ module ActsAsRecursiveTree
|
|
7
7
|
#
|
8
8
|
# * <tt>foreign_key</tt> - specifies the column name to use for tracking
|
9
9
|
# of the tree (default: +parent_id+)
|
10
|
-
def recursive_tree(parent_key: :parent_id, parent_type_column: nil)
|
10
|
+
def recursive_tree(parent_key: :parent_id, parent_type_column: nil, dependent: nil)
|
11
11
|
class_attribute(:_recursive_tree_config, instance_writer: false)
|
12
12
|
|
13
13
|
self._recursive_tree_config = Config.new(
|
14
14
|
model_class: self,
|
15
15
|
parent_key: parent_key.to_sym,
|
16
|
-
parent_type_column: parent_type_column.try(:to_sym)
|
16
|
+
parent_type_column: parent_type_column.try(:to_sym),
|
17
|
+
dependent: dependent
|
17
18
|
)
|
18
19
|
|
19
20
|
include ActsAsRecursiveTree::Model
|
@@ -16,7 +16,8 @@ module ActsAsRecursiveTree
|
|
16
16
|
has_many :children,
|
17
17
|
class_name: base_class.to_s,
|
18
18
|
foreign_key: _recursive_tree_config.parent_key,
|
19
|
-
inverse_of: :parent
|
19
|
+
inverse_of: :parent,
|
20
|
+
dependent: _recursive_tree_config.dependent
|
20
21
|
|
21
22
|
has_many :self_and_siblings,
|
22
23
|
through: :parent,
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'securerandom'
|
4
|
+
|
3
5
|
module ActsAsRecursiveTree
|
4
6
|
module Builders
|
5
7
|
#
|
@@ -12,40 +14,42 @@ module ActsAsRecursiveTree
|
|
12
14
|
|
13
15
|
class_attribute :traversal_strategy, instance_writer: false
|
14
16
|
|
15
|
-
attr_reader :klass, :ids, :
|
16
|
-
|
17
|
-
mattr_reader(:random) { Random.new }
|
17
|
+
attr_reader :klass, :ids, :without_ids
|
18
18
|
|
19
19
|
# Delegators for easier accessing config and query options
|
20
|
-
delegate :primary_key, :depth_column, :parent_key, :parent_type_column, to:
|
20
|
+
delegate :primary_key, :depth_column, :parent_key, :parent_type_column, to: :config
|
21
21
|
delegate :depth_present?, :depth, :condition, :ensure_ordering, to: :@query_opts
|
22
22
|
|
23
23
|
def initialize(klass, ids, exclude_ids: false, &block)
|
24
24
|
@klass = klass
|
25
|
-
@
|
26
|
-
@ids = ActsAsRecursiveTree::Options::Values.create(ids, @config)
|
25
|
+
@ids = ActsAsRecursiveTree::Options::Values.create(ids, klass._recursive_tree_config)
|
27
26
|
@without_ids = exclude_ids
|
28
27
|
|
29
|
-
@query_opts = get_query_options(block)
|
28
|
+
@query_opts = get_query_options(&block)
|
29
|
+
|
30
|
+
# random seed for the temp tables
|
31
|
+
@rand_int = SecureRandom.rand(1_000_000)
|
32
|
+
end
|
33
|
+
|
34
|
+
def recursive_temp_table
|
35
|
+
@recursive_temp_table ||= Arel::Table.new("recursive_#{klass.table_name}_#{@rand_int}_temp")
|
36
|
+
end
|
37
|
+
|
38
|
+
def travers_loc_table
|
39
|
+
@travers_loc_table ||= Arel::Table.new("traverse_#{@rand_int}_loc")
|
40
|
+
end
|
30
41
|
|
31
|
-
|
32
|
-
|
33
|
-
@travers_loc_table = Arel::Table.new("traverse_#{rand_int}_loc")
|
42
|
+
def config
|
43
|
+
klass._recursive_tree_config
|
34
44
|
end
|
35
45
|
|
36
46
|
#
|
37
47
|
# Constructs a new QueryOptions and yield it to the proc if one is present.
|
38
48
|
# Subclasses may override this method to provide sane defaults.
|
39
49
|
#
|
40
|
-
# @param proc [Proc] a proc or nil
|
41
|
-
#
|
42
50
|
# @return [ActsAsRecursiveTree::Options::QueryOptions] the new QueryOptions instance
|
43
|
-
def get_query_options(
|
44
|
-
|
45
|
-
|
46
|
-
proc&.call(opts)
|
47
|
-
|
48
|
-
opts
|
51
|
+
def get_query_options(&block)
|
52
|
+
ActsAsRecursiveTree::Options::QueryOptions.from(&block)
|
49
53
|
end
|
50
54
|
|
51
55
|
def base_table
|
@@ -71,11 +75,7 @@ module ActsAsRecursiveTree
|
|
71
75
|
end
|
72
76
|
|
73
77
|
def create_select_manger(column = nil)
|
74
|
-
projections =
|
75
|
-
travers_loc_table[column]
|
76
|
-
else
|
77
|
-
Arel.star
|
78
|
-
end
|
78
|
+
projections = column ? travers_loc_table[column] : Arel.star
|
79
79
|
|
80
80
|
select_mgr = travers_loc_table.project(projections).with(:recursive, build_cte_table)
|
81
81
|
|
@@ -85,10 +85,24 @@ module ActsAsRecursiveTree
|
|
85
85
|
def build_cte_table
|
86
86
|
Arel::Nodes::As.new(
|
87
87
|
travers_loc_table,
|
88
|
-
|
88
|
+
add_pg_cycle_detection(
|
89
|
+
build_base_select.union(build_union_select)
|
90
|
+
)
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
def add_pg_cycle_detection(union_query)
|
95
|
+
return union_query unless config.cycle_detection?
|
96
|
+
|
97
|
+
Arel::Nodes::InfixOperation.new(
|
98
|
+
'',
|
99
|
+
union_query,
|
100
|
+
Arel.sql("CYCLE #{primary_key} SET is_cycle USING path")
|
89
101
|
)
|
90
102
|
end
|
91
103
|
|
104
|
+
# Builds SQL:
|
105
|
+
# SELECT id, parent_id, 0 AS depth FROM base_table WHERE id = 123
|
92
106
|
def build_base_select
|
93
107
|
id_node = base_table[primary_key]
|
94
108
|
|
@@ -5,13 +5,14 @@ module ActsAsRecursiveTree
|
|
5
5
|
# Stores the configuration of one Model class
|
6
6
|
#
|
7
7
|
class Config
|
8
|
-
attr_reader :parent_key, :parent_type_column, :depth_column
|
8
|
+
attr_reader :parent_key, :parent_type_column, :depth_column, :dependent
|
9
9
|
|
10
|
-
def initialize(model_class:, parent_key:, parent_type_column:, depth_column: :recursive_depth)
|
10
|
+
def initialize(model_class:, parent_key:, parent_type_column:, depth_column: :recursive_depth, dependent: nil)
|
11
11
|
@model_class = model_class
|
12
12
|
@parent_key = parent_key
|
13
13
|
@parent_type_column = parent_type_column
|
14
14
|
@depth_column = depth_column
|
15
|
+
@dependent = dependent
|
15
16
|
end
|
16
17
|
|
17
18
|
#
|
@@ -20,5 +21,15 @@ module ActsAsRecursiveTree
|
|
20
21
|
def primary_key
|
21
22
|
@primary_key ||= @model_class.primary_key.to_sym
|
22
23
|
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Checks if SQL cycle detection can be used. This is currently supported only on PostgreSQL 14+.
|
27
|
+
# @return [TrueClass|FalseClass]
|
28
|
+
def cycle_detection?
|
29
|
+
return @cycle_detection if defined?(@cycle_detection)
|
30
|
+
|
31
|
+
@cycle_detection = @model_class.connection.adapter_name == 'PostgreSQL' &&
|
32
|
+
@model_class.connection.database_version >= 140_000
|
33
|
+
end
|
23
34
|
end
|
24
35
|
end
|
@@ -3,15 +3,15 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
RSpec.describe ActsAsRecursiveTree::Builders::Ancestors do
|
6
|
-
context '
|
6
|
+
context 'without additional setup' do
|
7
7
|
it_behaves_like 'build recursive query'
|
8
8
|
it_behaves_like 'ancestor query'
|
9
|
-
include_context '
|
9
|
+
include_context 'with ordering'
|
10
10
|
end
|
11
11
|
|
12
12
|
context 'with options' do
|
13
|
-
include_context '
|
14
|
-
it_behaves_like '
|
13
|
+
include_context 'with enforced ordering setup' do
|
14
|
+
it_behaves_like 'is adding ordering'
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -3,16 +3,16 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
RSpec.describe ActsAsRecursiveTree::Builders::Descendants do
|
6
|
-
context '
|
6
|
+
context 'without additional setup' do
|
7
7
|
it_behaves_like 'build recursive query'
|
8
8
|
it_behaves_like 'descendant query'
|
9
|
-
include_context '
|
9
|
+
include_context 'without ordering'
|
10
10
|
end
|
11
11
|
|
12
12
|
context 'with options' do
|
13
|
-
include_context '
|
13
|
+
include_context 'with enforced ordering setup' do
|
14
14
|
let(:ordering) { true }
|
15
|
-
it_behaves_like '
|
15
|
+
it_behaves_like 'is adding ordering'
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -3,16 +3,16 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
RSpec.describe ActsAsRecursiveTree::Builders::Leaves do
|
6
|
-
context '
|
6
|
+
context 'without additional setup' do
|
7
7
|
it_behaves_like 'build recursive query'
|
8
8
|
it_behaves_like 'descendant query'
|
9
|
-
include_context '
|
9
|
+
include_context 'without ordering'
|
10
10
|
end
|
11
11
|
|
12
12
|
context 'with options' do
|
13
|
-
include_context '
|
13
|
+
include_context 'with enforced ordering setup' do
|
14
14
|
let(:ordering) { true }
|
15
|
-
it_behaves_like '
|
15
|
+
it_behaves_like 'not adding ordering'
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -20,13 +20,13 @@ RSpec.describe ActsAsRecursiveTree::Options::Values do
|
|
20
20
|
let(:table) { Arel::Table.new('test_table') }
|
21
21
|
let(:attribute) { table['test_attr'] }
|
22
22
|
|
23
|
-
context 'invalid agurment' do
|
23
|
+
context 'with invalid agurment' do
|
24
24
|
it 'raises exception' do
|
25
25
|
expect { described_class.create(nil) }.to raise_exception(/is not supported/)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
context 'single value' do
|
29
|
+
context 'with single value' do
|
30
30
|
let(:single_value) { 3 }
|
31
31
|
|
32
32
|
it_behaves_like 'single values' do
|
@@ -38,8 +38,8 @@ RSpec.describe ActsAsRecursiveTree::Options::Values do
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
context 'multi value' do
|
42
|
-
context 'Array' do
|
41
|
+
context 'with multi value' do
|
42
|
+
context 'with Array' do
|
43
43
|
subject(:value) { described_class.create(array) }
|
44
44
|
|
45
45
|
let(:array) { [1, 2, 3] }
|
@@ -55,7 +55,7 @@ RSpec.describe ActsAsRecursiveTree::Options::Values do
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
context 'Range' do
|
58
|
+
context 'with Range' do
|
59
59
|
subject(:value) { described_class.create(range) }
|
60
60
|
|
61
61
|
let(:range) { 1..3 }
|
@@ -71,7 +71,7 @@ RSpec.describe ActsAsRecursiveTree::Options::Values do
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
-
context 'Relation' do
|
74
|
+
context 'with Relation' do
|
75
75
|
subject(:value) { described_class.create(relation, double) }
|
76
76
|
|
77
77
|
let(:relation) { Node.where(name: 'test') }
|
@@ -1,11 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_context 'with enforced ordering setup' do
|
2
4
|
let(:ordering) { false }
|
3
|
-
include_context 'base_setup' do
|
5
|
+
include_context 'with base_setup' do
|
4
6
|
let(:proc) { ->(config) { config.ensure_ordering! } }
|
5
7
|
end
|
6
8
|
end
|
7
9
|
|
8
|
-
RSpec.shared_context 'base_setup' do
|
10
|
+
RSpec.shared_context 'with base_setup' do
|
9
11
|
subject(:query) { builder.build.to_sql }
|
10
12
|
|
11
13
|
let(:model_id) { 1 }
|
@@ -32,30 +34,30 @@ RSpec.shared_examples 'basic recursive examples' do
|
|
32
34
|
end
|
33
35
|
|
34
36
|
RSpec.shared_examples 'build recursive query' do
|
35
|
-
context 'simple id' do
|
37
|
+
context 'with simple id' do
|
36
38
|
context 'with simple class' do
|
37
|
-
include_context 'base_setup' do
|
39
|
+
include_context 'with base_setup' do
|
38
40
|
let(:model_class) { Node }
|
39
41
|
it_behaves_like 'basic recursive examples'
|
40
42
|
end
|
41
43
|
end
|
42
44
|
|
43
45
|
context 'with class with different parent key' do
|
44
|
-
include_context 'base_setup' do
|
46
|
+
include_context 'with base_setup' do
|
45
47
|
let(:model_class) { NodeWithOtherParentKey }
|
46
48
|
it_behaves_like 'basic recursive examples'
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
50
52
|
context 'with Subclass' do
|
51
|
-
include_context 'base_setup' do
|
53
|
+
include_context 'with base_setup' do
|
52
54
|
let(:model_class) { Floor }
|
53
55
|
it_behaves_like 'basic recursive examples'
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
57
59
|
context 'with polymorphic parent relation' do
|
58
|
-
include_context 'base_setup' do
|
60
|
+
include_context 'with base_setup' do
|
59
61
|
let(:model_class) { NodeWithPolymorphicParent }
|
60
62
|
it_behaves_like 'basic recursive examples'
|
61
63
|
end
|
@@ -64,34 +66,34 @@ RSpec.shared_examples 'build recursive query' do
|
|
64
66
|
end
|
65
67
|
|
66
68
|
RSpec.shared_examples 'ancestor query' do
|
67
|
-
include_context 'base_setup'
|
69
|
+
include_context 'with base_setup'
|
68
70
|
|
69
71
|
it { is_expected.to match(/"#{builder.travers_loc_table.name}"."#{model_class._recursive_tree_config.parent_key}" = "#{model_class.table_name}"."#{model_class.primary_key}"/) }
|
70
72
|
end
|
71
73
|
|
72
74
|
RSpec.shared_examples 'descendant query' do
|
73
|
-
include_context 'base_setup'
|
75
|
+
include_context 'with base_setup'
|
74
76
|
|
75
77
|
it { is_expected.to match(/"#{model_class.table_name}"."#{model_class._recursive_tree_config.parent_key}" = "#{builder.travers_loc_table.name}"."#{model_class.primary_key}"/) }
|
76
78
|
it { is_expected.to match(/#{Regexp.escape(builder.travers_loc_table.project(builder.travers_loc_table[model_class.primary_key]).to_sql)}/) }
|
77
79
|
end
|
78
80
|
|
79
|
-
RSpec.shared_context '
|
80
|
-
include_context 'base_setup' do
|
81
|
-
it_behaves_like '
|
81
|
+
RSpec.shared_context 'with ordering' do
|
82
|
+
include_context 'with base_setup' do
|
83
|
+
it_behaves_like 'is adding ordering'
|
82
84
|
end
|
83
85
|
end
|
84
86
|
|
85
|
-
RSpec.shared_context '
|
86
|
-
include_context 'base_setup' do
|
87
|
-
it_behaves_like '
|
87
|
+
RSpec.shared_context 'without ordering' do
|
88
|
+
include_context 'with base_setup' do
|
89
|
+
it_behaves_like 'not adding ordering'
|
88
90
|
end
|
89
91
|
end
|
90
92
|
|
91
|
-
RSpec.shared_examples '
|
93
|
+
RSpec.shared_examples 'is adding ordering' do
|
92
94
|
it { is_expected.to match(/ORDER BY #{Regexp.escape(builder.recursive_temp_table[model_class._recursive_tree_config.depth_column].asc.to_sql)}/) }
|
93
95
|
end
|
94
96
|
|
95
|
-
RSpec.shared_examples '
|
97
|
+
RSpec.shared_examples 'not adding ordering' do
|
96
98
|
it { is_expected.not_to match(/ORDER BY/) }
|
97
99
|
end
|
@@ -3,13 +3,10 @@
|
|
3
3
|
# Helper methods for simple tree creation
|
4
4
|
module TreeMethods
|
5
5
|
def create_tree(max_level, current_level: 0, node: nil, create_node_info: false, stop_at: -1)
|
6
|
-
node
|
6
|
+
node ||= Node.create!(name: 'root')
|
7
7
|
|
8
8
|
1.upto(max_level - current_level) do |index|
|
9
|
-
child = node.children.create!(
|
10
|
-
name: "child #{index} - level #{current_level}",
|
11
|
-
active: stop_at > current_level
|
12
|
-
)
|
9
|
+
child = node.children.create!(name: "child #{index} - level #{current_level}", active: stop_at > current_level)
|
13
10
|
|
14
11
|
child.create_node_info(status: stop_at > current_level ? 'foo' : 'bar') if create_node_info
|
15
12
|
|
@@ -21,7 +18,6 @@ module TreeMethods
|
|
21
18
|
stop_at: stop_at
|
22
19
|
)
|
23
20
|
end
|
24
|
-
|
25
21
|
node
|
26
22
|
end
|
27
23
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_recursive_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wolfgang Wedelich-John
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-08-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|