pg_tags_on 0.1.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -2
  3. data/LICENSE.txt +1 -1
  4. data/README.md +30 -22
  5. data/lib/pg_tags_on/active_record/base.rb +1 -1
  6. data/lib/pg_tags_on/repositories/array_jsonb_repository.rb +47 -24
  7. data/lib/pg_tags_on/repositories/array_repository.rb +31 -18
  8. data/lib/pg_tags_on/repositories/base_repository.rb +39 -4
  9. data/lib/pg_tags_on/validations/validator.rb +4 -4
  10. data/lib/pg_tags_on/version.rb +1 -1
  11. metadata +25 -141
  12. data/.gitignore +0 -12
  13. data/.rspec +0 -3
  14. data/.rubocop.yml +0 -9
  15. data/CODE_OF_CONDUCT.md +0 -74
  16. data/Gemfile +0 -6
  17. data/Gemfile.lock +0 -91
  18. data/Rakefile +0 -16
  19. data/bin/console +0 -16
  20. data/bin/setup +0 -8
  21. data/pg_tags_on.gemspec +0 -38
  22. data/spec/array_integers/records_spec.rb +0 -47
  23. data/spec/array_integers/tag_ops_spec.rb +0 -65
  24. data/spec/array_integers/taggings_spec.rb +0 -27
  25. data/spec/array_integers/tags_spec.rb +0 -53
  26. data/spec/array_jsonb/records_spec.rb +0 -89
  27. data/spec/array_jsonb/tag_ops_spec.rb +0 -115
  28. data/spec/array_jsonb/taggings_spec.rb +0 -27
  29. data/spec/array_jsonb/tags_spec.rb +0 -41
  30. data/spec/array_strings/records_spec.rb +0 -61
  31. data/spec/array_strings/tag_ops_spec.rb +0 -65
  32. data/spec/array_strings/taggings_spec.rb +0 -27
  33. data/spec/array_strings/tags_spec.rb +0 -54
  34. data/spec/config/database.yml +0 -6
  35. data/spec/configuration_spec.rb +0 -48
  36. data/spec/helpers/database_helpers.rb +0 -46
  37. data/spec/spec_helper.rb +0 -39
  38. data/spec/support/factory.rb +0 -47
  39. data/spec/tags_query_spec.rb +0 -31
  40. data/spec/validator_spec.rb +0 -40
  41. data/tasks/benchmark.rake +0 -58
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcce5c8d438aa1e92aa724d7ba14275a8f8a40e7515c33cb341eb8270a7b9f57
4
- data.tar.gz: 7a61cf5537c72678f82692a999c67b0d5392b675bc5db24593b47341b42a9d74
3
+ metadata.gz: c61bd469e5458dd169e871a3af323806c36380beba46a575506d33cd18c730dd
4
+ data.tar.gz: c88ad1665a25c7e9ec406de248bdd0e845c5a1fe20d8fffd93e7e979a5a227fd
5
5
  SHA512:
6
- metadata.gz: 7330d881597fd28e77bd43ea44054eb9d3fe1f88e48afa00e61820d972afcf6255bbbd4bbb3af1b5061ab38adefa379bdf28a2f913456382907cfdaf8b806ed5
7
- data.tar.gz: 25cffbb4603b83da2812e890a1b2ccd4d8c5cf4c0293009b6b4d91075ddc592dca8b9706f8ce73f7fcf30b57a1c6e68807da637571edde8fc2e99131c6d079bb
6
+ metadata.gz: 05c51681cf87704fb8363f147470e5d4ce85a1c7390f064c055f7da19cdb79a419d046eed1432992ca0e002afe50685fff0d1f84f14b472f8ebc4bf6e7df5d44
7
+ data.tar.gz: 7481b613c53054dd15ba0cef620dd25ba3f1d09d8b10b5d5a69d03ddc1f02c5c3cedca1f9787bc68fdbb925dd612ff0bc18ca6b1b7413fce70aebb81e50bb887
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
- == 0.1.0 / 2020-03-11
1
+ # Changelog
2
+
3
+ ## [0.4.0] - 2020-06-02
4
+ - bump gems versions
5
+ - ISSUE_TEMPLATE.md
6
+
7
+ ## [0.3.0] - 2020-04-15
8
+ - apply PR for gem versions
9
+ - fixed Arel deprecation warning
10
+ - changed validation param names
11
+
12
+ ## [0.2.0] - 2020-09-02
13
+ - Added support for multiple tags creation and deletion
14
+ - Optimizations
15
+ - Updated specs and documentation
16
+
17
+ ## [0.1.4] - 2020-04-12
18
+ - Added support for returning columns after create,update or delete tags operations
19
+ - Updated specs
20
+ - Updated documentation
21
+
22
+ ## [0.1.0] - 2020-03-11
23
+ - First release
2
24
 
3
- First release
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2019 Catalin Marinescu
3
+ Copyright (c) 2019-2020 Catalin Marinescu
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # PgTagsOn
2
2
 
3
- ```pg_tags_on``` is a gem that makes working with tags stored in a Postgresql column easy. Supported column types are: ```character varying[]```, ```text[]```, ```integer[]``` and ```jsonb[]```.
3
+ ```pg_tags_on``` - keep tags in a column. Supported column types are: ```character varying[]```, ```text[]```, ```integer[]``` and ```jsonb[]```.
4
4
 
5
5
 
6
6
  Requirements:
@@ -15,7 +15,7 @@ Note: this gem is in early stage of development.
15
15
  - [Installation](#installation)
16
16
  - [Usage](#usage)
17
17
  - [ActiveRecord model setup](#activerecord-model-setup)
18
- - [Records queries](#records-queries)
18
+ - [Records queries](#queries)
19
19
  - [Tags](#tags)
20
20
  - [Set record's tags](#set-records-tags)
21
21
  - [Configuration](#configuration)
@@ -42,7 +42,7 @@ Or install it yourself as:
42
42
  ## Usage
43
43
  ### ActiveRecord model setup
44
44
 
45
- One or multiple columns from a model can be specified:
45
+ One or multiple columns that store tags can be specified:
46
46
 
47
47
  ```ruby
48
48
  class Entity < ActiveRecord::Base
@@ -51,26 +51,26 @@ class Entity < ActiveRecord::Base
51
51
  end
52
52
  ```
53
53
 
54
- Validations for max. number of tags and max. tag length can be added and errors will be injected into model's ```errors``` object.
55
-
54
+ In ```jsonb[]``` columns, by default each tag is stored as an object with a single key. For example ```{"tag": "rubygems"}```. If you store multiple attributes in the objects, then you must set also ```has_attributes: true```.
56
55
 
57
56
  ```ruby
58
57
  class Entity < ActiveRecord::Base
59
- pg_tags_on :tags, limit: 10, tag_length: 50 # limit to 10 tags and 50 chars. per tag.
58
+ pg_tags_on :tags, key: :tag # => [{tag: 'alpha'}, {tag: 'beta'}]
59
+ pg_tags_on :other_tags, key: :tag, has_attributes: true # => [{tag: 'alpha', created_by: 'mike', ...}, {tag: 'beta', created_by: 'john', ...}]
60
60
  end
61
61
  ```
62
62
 
63
- For ```jsonb[]``` you'll have to specify the key for the tag value. If you store multiple attributes in the objects then you must set also ```has_attributes: true```.
63
+ ##### Validations
64
+ Maximum number of tags and maximum tag length can be validated. Errors will be injected into model's ```errors``` object.
64
65
 
65
66
  ```ruby
66
67
  class Entity < ActiveRecord::Base
67
- pg_tags_on :tags, key: :tag # => [{tag: 'alpha'}, {tag: 'beta'}]
68
- pg_tags_on :other_tags, key: :tag, has_attributes: true # => [{tag: 'alpha', created_by: 'mike', ...}, {tag: 'beta', created_by: 'john', ...}]
68
+ pg_tags_on :tags, limit: 10, length: 50 # limit to 10 tags and 50 chars. per tag.
69
69
  end
70
70
  ```
71
71
 
72
- ### Records Queries
73
- ```pg_tags_on``` registers ```Tags``` class in model's predicate builder, so you can filter the records by tags as you are usually doing in Rails. Class name can be changed if you have conflicts or you don't like it, see the [configuration](#configuration) section.
72
+ ### Queries
73
+ ```pg_tags_on``` registers ```Tags``` class in model's predicate builder, so records can be filtered using ActiveRecord's DSL. Class name can be changed if you have conflicts or you don't like it, see the [configuration](#configuration) section.
74
74
 
75
75
  * Find records by tag:
76
76
 
@@ -81,25 +81,25 @@ Entity.where(tags: Tags.one('alpha'))
81
81
  * Find records that have exact same tags as the list, order is not important:
82
82
 
83
83
  ```ruby
84
- Entity.where(tags: Tags.eq('alpha', 'beta', 'gamma')) # Array argument is allowed, too
84
+ Entity.where(tags: Tags.eq('alpha', 'beta', 'gamma')) # Array argument is allowed too for every method.
85
85
  ```
86
86
 
87
87
  * Find records that includes all the tags from the list:
88
88
 
89
89
  ```ruby
90
- Entity.where(tags: Tags.all('alpha', 'beta', 'gamma')) # Array argument is allowed, too
90
+ Entity.where(tags: Tags.all('alpha', 'beta', 'gamma'))
91
91
  ```
92
92
 
93
93
  * Find records that includes any tag from the list:
94
94
 
95
95
  ```ruby
96
- Entity.where(tags: Tags.any('alpha', 'beta', 'gamma')) # Array argument is allowed, too
96
+ Entity.where(tags: Tags.any('alpha', 'beta', 'gamma'))
97
97
  ```
98
98
 
99
99
  * Find records that have all the tags included in the list:
100
100
 
101
101
  ```ruby
102
- Entity.where(tags: Tags.in('alpha', 'beta', 'gamma')) # Array argument is allowed, too
102
+ Entity.where(tags: Tags.in('alpha', 'beta', 'gamma'))
103
103
  ```
104
104
 
105
105
  All the above queries supports negation operator. Example:
@@ -140,24 +140,32 @@ Entity.tags.taggings
140
140
  => [#<PgTagsOn::Tag name: "alpha", entity_id: 1>, #<PgTagsOn::Tag name: "beta", entity_id: 1>, #<PgTagsOn::Tag name: "alpha", entity_id: 2>, ... ]
141
141
  ```
142
142
 
143
- Create, update and delete methods are using, for performance reasons, Postgresql functions to manipulate the arrays, so ActiveRecord models are not instantiated. A frequent problem is to ensure uniqueness of the tags for a record, and this can be achieved at the database level with a before create/update row trigger.
143
+ Create, update and delete methods are using, for performance reasons, Postgresql functions to manipulate the arrays, so ActiveRecord models are not instantiated. A frequent problem is to ensure uniqueness of the tags for a record, and this can be achieved at the database level by creating a before create/update row trigger.
144
144
 
145
145
  ```ruby
146
146
  # create
147
- Entity.tags.create('alpha') # add tag to all records
148
- Entity.where(...).tags.create('alpha') # add tag to filtered records
147
+ Entity.tags.create('alpha') # add tag to all records
148
+ Entity.tags.create(%w[alpha beta gamma]) # add many tags to all records
149
+ Entity.where(...).tags.create('alpha') # add tag to filtered records
149
150
 
150
151
  # update
151
152
  Entity.tags.update('alpha', 'a') # rename tag for all records
152
153
  Entity.where(...).tags.update('alpha', 'a') # rename tag for filtered records
153
154
 
154
155
  # delete
155
- Entity.tags.delete('alpha') # delete tag from all records
156
- Entity.where(...).tags.delete('alpha') # delete tag from filtered records
156
+ Entity.tags.delete('alpha') # delete tag from all records
157
+ Entity.tags.delete(%w[alpha beta gamma]) # delete many tags from all records
158
+ Entity.where(...).tags.delete('alpha') # delete tag from filtered records
159
+
160
+ # any of these methods accepts :returning option
161
+ Entity.tags.update('alpha', 'a', returning: %w[id tags])
162
+ => [[1, '{a}'], ...]
157
163
  ```
158
164
 
165
+
166
+
159
167
  ### Set record's tags
160
- By default ```pg_tags_on``` does not add any logic in the way that the tags are set and saved in database. It'll let all the transformations, like lowercase, strip spaces, unique etc..., at the programmer choice.
168
+ By default ```pg_tags_on``` does not add any logic in the way that the tags are set for the model and saved in database. It'll let all the transformations, like lowercase, strip spaces, unique etc..., at the programmer choice. Specific setters can be implemented.
161
169
 
162
170
 
163
171
  ### Configuration
@@ -181,7 +189,7 @@ rake pg_tags_on:benchmark
181
189
  1. Fork it ( http://github.com/cata-m/pg_tags_on/fork )
182
190
  2. Install gem dependencies: ```bundle install```
183
191
  3. Create a new feature or fix branch like: 'feature/new-feature' or 'fix/fix-some-issues'
184
- 4. Make sure all tests pass: ```bundle exec rake spec```
192
+ 4. Write your modifications and make sure all tests pass: ```bundle exec rake spec```
185
193
  5. Commit your changes: git commit -am 'your changes'
186
194
  6. Push to the branch
187
195
  7. Create new pull request
@@ -12,7 +12,7 @@ module PgTagsOn
12
12
 
13
13
  pg_tags_on_init_model unless @pg_tags_on_init_model
14
14
  pg_tags_on_settings[name] = options.deep_symbolize_keys
15
- validates(name, 'pg_tags_on/tags': true) if %i[limit tag_length].any? { |k| options[k] }
15
+ validates(name, 'pg_tags_on/tags': true) if %i[limit length].any? { |k| options[k] }
16
16
  instance_eval <<-RUBY, __FILE__, __LINE__ + 1
17
17
  scope :#{name}, -> { PgTagsOn::Repository.new(self, "#{name}") }
18
18
  RUBY
@@ -4,40 +4,59 @@ module PgTagsOn
4
4
  module Repositories
5
5
  # Operatons for 'jsonb[]' column type
6
6
  class ArrayJsonbRepository < ArrayRepository
7
- def create(tag)
8
- with_normalized_tags(tag) do |n_tag|
9
- super(n_tag)
10
- end
7
+ def create(tag_or_tags, returning: nil)
8
+ tags = normalize_tags(tag_or_tags)
9
+
10
+ super(tags, returning: returning)
11
11
  end
12
12
 
13
- def update(tag, new_tag)
14
- with_normalized_tags(tag, new_tag) do |n_tag, n_new_tag|
15
- sql_set = <<-SQL.strip
16
- #{column_name}[index] = #{column_name}[index] || $2
17
- SQL
13
+ def update(tag, new_tag, returning: nil)
14
+ n_tag, n_new_tag = normalize_tags([tag, new_tag])
18
15
 
19
- update_tag(n_tag, sql_set, [query_attribute(n_new_tag.to_json)])
20
- end
16
+ sql_set = <<-SQL.strip
17
+ #{column_name}[index] = #{column_name}[index] || $2
18
+ SQL
19
+
20
+ update_tag(n_tag,
21
+ sql_set,
22
+ bindings: [query_attribute(n_new_tag.to_json)],
23
+ returning: returning)
21
24
  end
22
25
 
23
- def delete(tag)
24
- with_normalized_tags(tag) do |n_tag|
25
- sql_set = <<-SQL.strip
26
- #{column_name} = #{column_name}[1:index-1] || #{column_name}[index+1:2147483647]
27
- SQL
26
+ def delete(tag_or_tags, returning: nil)
27
+ tags = normalize_tags(tag_or_tags)
28
+ sm = build_tags_select_manager
28
29
 
29
- update_tag(n_tag, sql_set)
30
+ tags.each do |tag|
31
+ sm.where(arel_infix_operation('@>', Arel.sql('tag'), bind_for(tag.to_json, nil)).not)
30
32
  end
33
+
34
+ rel = klass.where(column_name => PgTagsOn.query_class.any(tag_or_tags))
35
+ value = arel_function('array', sm)
36
+
37
+ perform_update(rel, { column_name => value }, returning: returning)
31
38
  end
32
39
 
33
40
  private
34
41
 
35
- def with_normalized_tags(*tags, &block)
36
- normalized_tags = Array.wrap(tags).flatten.map do |tag|
37
- key? && Array.wrap(key).reverse.inject(tag) { |a, n| { n => a } } || tag
38
- end
42
+ # Returns SelectManager for unnested tags.
43
+ # sql: select tag from ( select unnest(tags_jsonb) as tag ) as _tags
44
+ def build_tags_select_manager
45
+ Arel::SelectManager.new
46
+ .project('tag')
47
+ .from(Arel::SelectManager.new
48
+ .project(unnest.as('tag'))
49
+ .as('_tags'))
50
+ end
51
+
52
+ def normalize_tags(tag_or_tags)
53
+ tags = Array.wrap(tag_or_tags)
54
+
55
+ return tags unless key?
39
56
 
40
- block.call(*normalized_tags)
57
+ tags.map do |tag|
58
+ Array.wrap(key).reverse.inject(tag) { |a, n| { n => a } }
59
+ end
41
60
  end
42
61
 
43
62
  def array_to_recordset
@@ -68,7 +87,7 @@ module PgTagsOn
68
87
  .where(arel_infix_operation('@>', column, value))
69
88
  end
70
89
 
71
- def update_tag(tag, set_sql, bindings = [])
90
+ def update_tag(tag, set_sql, bindings: [], returning: nil)
72
91
  subquery = taggings_with_ordinality_query(tag)
73
92
  .where(arel_table[:id].in(arel_sql(klass.reselect('id').to_sql)))
74
93
 
@@ -80,8 +99,12 @@ module PgTagsOn
80
99
  WHERE #{table_name}.id = records.id
81
100
  SQL
82
101
 
102
+ sql += " RETURNING #{Array.wrap(returning).join(', ')}" if returning.present?
103
+
83
104
  bindings = [query_attribute(tag.to_json)] + Array.wrap(bindings)
84
- klass.connection.exec_query(sql, 'SQL', bindings)
105
+ result = klass.connection.exec_query(sql, 'SQL', bindings).rows
106
+
107
+ returning ? result : true
85
108
  end
86
109
  end
87
110
  end
@@ -42,30 +42,43 @@ module PgTagsOn
42
42
  all.count
43
43
  end
44
44
 
45
- def create(tag)
46
- return true if tag.blank?
45
+ def create(tag_or_tags, returning: nil)
46
+ value = arel_array_cat(arel_column, bind_for(Array.wrap(tag_or_tags)))
47
47
 
48
- klass.update_all(column_name => arel_array_cat(arel_column, bind_for(Array.wrap(tag))))
48
+ perform_update(klass, { column_name => value }, returning: returning)
49
49
  end
50
50
 
51
- def update(tag, new_tag)
52
- return true if tag.blank? || new_tag.blank? || tag == new_tag
51
+ def update(tag, new_tag, returning: nil)
52
+ rel = klass.where(column_name => PgTagsOn.query_class.one(tag))
53
+ value = arel_array_replace(arel_column, bind_for(tag), bind_for(new_tag))
53
54
 
54
- klass
55
- .where(column_name => PgTagsOn.query_class.one(tag))
56
- .update_all(column_name => arel_array_replace(arel_column, bind_for(tag), bind_for(new_tag)))
55
+ perform_update(rel, { column_name => value }, returning: returning)
57
56
  end
58
57
 
59
- def delete(tag)
60
- klass
61
- .where(column_name => PgTagsOn.query_class.one(tag))
62
- .update_all(column_name => arel_array_remove(arel_column, bind_for(tag)))
58
+ def delete(tag_or_tags, returning: nil)
59
+ tags = Array.wrap(tag_or_tags)
60
+ rel = klass.where(column_name => PgTagsOn.query_class.any(tags))
61
+ sm = Arel::SelectManager.new
62
+ .project(unnest)
63
+ .except(
64
+ Arel::SelectManager.new
65
+ .project(arel_unnest(arel_cast(bind_for(tags), native_column_type)))
66
+ )
67
+ value = arel_function('array', sm)
68
+
69
+ perform_update(rel, { column_name => value }, returning: returning)
63
70
  end
64
71
 
65
72
  private
66
73
 
67
- def array_to_recordset
68
- unnest
74
+ def perform_update(rel, updates, returning: nil)
75
+ update_manager = get_update_manager(rel, updates)
76
+ sql, binds = klass.connection.send :to_sql_and_binds, update_manager
77
+ sql += " RETURNING #{Array.wrap(returning).join(', ')}" if returning.present?
78
+
79
+ result = klass.connection.exec_query(sql, 'SQL', binds).rows
80
+
81
+ returning ? result : true
69
82
  end
70
83
 
71
84
  def taggings_query
@@ -78,14 +91,14 @@ module PgTagsOn
78
91
  .as('taggings')
79
92
  end
80
93
 
81
- def ref
82
- "#{table_name}.#{column_name}"
83
- end
84
-
85
94
  def unnest
86
95
  arel_unnest(arel_column)
87
96
  end
88
97
 
98
+ def array_to_recordset
99
+ unnest
100
+ end
101
+
89
102
  def unnest_with_ordinality(alias_table: 't')
90
103
  "#{unnest.to_sql} WITH ORDINALITY #{alias_table}(name, index)"
91
104
  end
@@ -28,10 +28,6 @@ module PgTagsOn
28
28
  @table_name ||= klass.table_name
29
29
  end
30
30
 
31
- def cast_type
32
- @cast_type ||= klass.type_for_attribute(column_name)
33
- end
34
-
35
31
  def arel_table
36
32
  klass.arel_table
37
33
  end
@@ -39,6 +35,45 @@ module PgTagsOn
39
35
  def arel_column
40
36
  arel_table[column_name]
41
37
  end
38
+
39
+ # Returns ActiveRecord Column instance
40
+ def ar_column
41
+ @ar_column ||= klass.columns_hash[column_name.to_s]
42
+ end
43
+
44
+ def ref
45
+ "#{table_name}.#{column_name}"
46
+ end
47
+
48
+ # Returns Type instance
49
+ def cast_type
50
+ @cast_type ||= klass.type_for_attribute(column_name)
51
+ end
52
+
53
+ # Returns db type as string.
54
+ # Ex: character varying[], integer[]
55
+ def native_column_type
56
+ type = ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES.fetch(cast_type.type)[:name]
57
+ type += '[]' if ar_column.array?
58
+
59
+ type
60
+ end
61
+
62
+ # Method copied from ActiveRecord as there is no way to inject sql into update manager.
63
+ def get_update_manager(rel, updates)
64
+ raise ArgumentError, 'Empty list of attributes to change' if updates.blank?
65
+
66
+ stmt = ::Arel::UpdateManager.new
67
+ stmt.table(arel_table)
68
+ stmt.key = arel_table[klass.primary_key]
69
+ stmt.take(rel.arel.limit)
70
+ stmt.offset(rel.arel.offset)
71
+ stmt.order(*rel.arel.orders)
72
+ stmt.wheres = rel.arel.constraints
73
+ stmt.set rel.send(:_substitute_values, updates)
74
+
75
+ stmt
76
+ end
42
77
  end
43
78
  end
44
79
  end
@@ -4,7 +4,7 @@ module PgTagsOn
4
4
  # Validator for max. number of tags and max. tag length.
5
5
  #
6
6
  # class Entity
7
- # pg_tags_on :tags, limit: 20, tag_length: 64
7
+ # pg_tags_on :tags, limit: 20, length: 64
8
8
  # end
9
9
  #
10
10
  class TagsValidator < ActiveModel::EachValidator
@@ -15,7 +15,7 @@ module PgTagsOn
15
15
 
16
16
  def validate_each(record, attribute, value)
17
17
  validate_limit(record, attribute, value)
18
- validate_tag_length(record, attribute, value)
18
+ validate_length(record, attribute, value)
19
19
 
20
20
  record.errors.present?
21
21
  end
@@ -31,8 +31,8 @@ module PgTagsOn
31
31
  record.errors.add(attr, "size exceeded #{limit} tags") if value.size > limit.to_i
32
32
  end
33
33
 
34
- def validate_tag_length(record, attr, value)
35
- limit, key = klass.pg_tags_on_options_for(attr).values_at(:tag_length, :key)
34
+ def validate_length(record, attr, value)
35
+ limit, key = klass.pg_tags_on_options_for(attr).values_at(:length, :key)
36
36
  return true unless limit && value
37
37
 
38
38
  value.map! { |tag| tag.with_indifferent_access.dig(*key) } if key