metka 2.0.3 → 2.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE.md +15 -0
- data/.github/workflows/lint_code.yml +21 -0
- data/.github/workflows/lint_docs.yml +57 -0
- data/.github/workflows/specs.yml +86 -0
- data/.gitignore +3 -1
- data/.mdlrc +1 -0
- data/.rubocop-md.yml +20 -0
- data/.rubocop.yml +1 -2
- data/.ruby-version +1 -0
- data/CODE_OF_CONDUCT.md +3 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +127 -129
- data/README.md +115 -49
- data/forspell.dict +7 -0
- data/gemfiles/rails52.gemfile +1 -1
- data/gemfiles/rails6.gemfile +2 -2
- data/gemfiles/rails61.gemfile +6 -0
- data/gemfiles/railsmain.gemfile +5 -0
- data/gemfiles/rubocop.gemfile +4 -0
- data/lib/metka/generic_parser.rb +2 -2
- data/lib/metka/model.rb +6 -6
- data/lib/metka/query_builder.rb +8 -6
- data/lib/metka/query_builder/base_query.rb +37 -16
- data/lib/metka/version.rb +1 -1
- data/metka.gemspec +1 -2
- metadata +16 -22
- data/.github/workflows/continuous-integration-workflow.yml +0 -11
- data/.travis.yml +0 -35
- data/gemfiles/rails5.gemfile +0 -6
data/README.md
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
[![Gem Version](https://badge.fury.io/rb/metka.svg)](https://badge.fury.io/rb/metka)
|
2
|
-
[![Build Status](https://
|
2
|
+
[![Build Status](https://github.com/jetrockets/metka/workflows/Specs/badge.svg?branch=master)](https://github.com/jetrockets/metka/actions)
|
3
3
|
[![Open Source Helpers](https://www.codetriage.com/jetrockets/metka/badges/users.svg)](https://www.codetriage.com/jetrockets/metka)
|
4
4
|
|
5
5
|
# Metka
|
6
6
|
|
7
7
|
Rails gem to manage tags with PostgreSQL array columns.
|
8
8
|
|
9
|
+
:exclamation: Requirements:
|
10
|
+
|
11
|
+
* Ruby ~> 2.5
|
12
|
+
* Rails >= 5.2 (for Rails 5.1 and 5.0 use version <2.1.0)
|
13
|
+
|
9
14
|
## Installation
|
10
15
|
|
11
16
|
Add this line to your application's Gemfile:
|
@@ -16,11 +21,15 @@ gem 'metka'
|
|
16
21
|
|
17
22
|
And then execute:
|
18
23
|
|
19
|
-
|
24
|
+
```bash
|
25
|
+
bundle
|
26
|
+
```
|
20
27
|
|
21
28
|
Or install it yourself as:
|
22
29
|
|
23
|
-
|
30
|
+
```bash
|
31
|
+
gem install metka
|
32
|
+
```
|
24
33
|
|
25
34
|
## Tag objects
|
26
35
|
|
@@ -32,9 +41,9 @@ rails g migration CreateSongs
|
|
32
41
|
class CreateSongs < ActiveRecord::Migration[5.0]
|
33
42
|
def change
|
34
43
|
create_table :songs do |t|
|
35
|
-
t.string
|
36
|
-
t.string
|
37
|
-
t.string
|
44
|
+
t.string :title
|
45
|
+
t.string :tags, array: true, default: [], index: { using: :gin }
|
46
|
+
t.string :genres, array: true, default: [], index: { using: :gin }
|
38
47
|
t.timestamps
|
39
48
|
end
|
40
49
|
end
|
@@ -55,140 +64,167 @@ end
|
|
55
64
|
## Find tagged objects
|
56
65
|
|
57
66
|
### .with_all_#{column_name}
|
67
|
+
|
58
68
|
```ruby
|
59
69
|
Song.with_all_tags('top')
|
60
|
-
|
70
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
61
71
|
|
62
72
|
Song.with_all_tags('top, 1990')
|
63
|
-
|
73
|
+
#=> []
|
64
74
|
|
65
75
|
Song.with_all_tags('')
|
66
|
-
|
76
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
77
|
+
|
78
|
+
Song.with_all_tags(nil)
|
79
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
67
80
|
|
68
81
|
Song.with_all_genres('rock')
|
69
|
-
|
82
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
70
83
|
```
|
71
84
|
|
72
85
|
### .with_any_#{column_name}
|
86
|
+
|
73
87
|
```ruby
|
74
88
|
Song.with_any_tags('chill')
|
75
|
-
|
89
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
76
90
|
|
77
91
|
Song.with_any_tags('chill, 1980')
|
78
|
-
|
92
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
79
93
|
|
80
94
|
Song.with_any_tags('')
|
81
|
-
|
95
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
96
|
+
|
97
|
+
Song.with_any_tags(nil)
|
98
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
82
99
|
|
83
100
|
Song.with_any_genres('rock, rap')
|
84
|
-
|
101
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
85
102
|
```
|
103
|
+
|
86
104
|
### .without_all_#{column_name}
|
105
|
+
|
87
106
|
```ruby
|
88
107
|
Song.without_all_tags('top')
|
89
|
-
|
108
|
+
#=> []
|
90
109
|
|
91
110
|
Song.without_all_tags('top, 1990')
|
92
|
-
|
111
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
93
112
|
|
94
113
|
Song.without_all_tags('')
|
95
|
-
|
114
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
115
|
+
|
116
|
+
Song.without_all_tags(nil)
|
117
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
96
118
|
|
97
119
|
Song.without_all_genres('rock, pop')
|
98
|
-
|
120
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
99
121
|
|
100
122
|
Song.without_all_genres('rock')
|
101
|
-
|
123
|
+
#=> []
|
102
124
|
```
|
103
125
|
|
104
126
|
### .without_any_#{column_name}
|
127
|
+
|
105
128
|
```ruby
|
106
129
|
Song.without_any_tags('top, 1990')
|
107
|
-
|
130
|
+
#=> []
|
108
131
|
|
109
132
|
Song.without_any_tags('1990, 1980')
|
110
|
-
|
133
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
111
134
|
|
112
135
|
Song.without_any_genres('rock, pop')
|
113
|
-
|
136
|
+
#=> []
|
114
137
|
|
115
138
|
Song.without_any_genres('')
|
116
|
-
|
139
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
140
|
+
|
141
|
+
Song.without_any_genres(nil)
|
142
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
117
143
|
```
|
118
144
|
|
119
145
|
### .tagged_with
|
146
|
+
|
120
147
|
```ruby
|
121
148
|
Song.tagged_with('top')
|
122
|
-
|
149
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
123
150
|
|
124
151
|
Song.tagged_with('top, 1990')
|
125
|
-
|
152
|
+
#=> []
|
126
153
|
|
127
154
|
Song.tagged_with('')
|
128
|
-
|
155
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
156
|
+
|
157
|
+
Song.tagged_with(nil)
|
158
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
129
159
|
|
130
160
|
Song.tagged_with('rock')
|
131
|
-
|
161
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
132
162
|
|
133
163
|
Song.tagged_with('rock', join_operator: Metka::And)
|
134
|
-
|
164
|
+
#=> []
|
135
165
|
|
136
166
|
Song.tagged_with('chill', any: true)
|
137
|
-
|
167
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
138
168
|
|
139
169
|
Song.tagged_with('chill, 1980', any: true)
|
140
|
-
|
170
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
141
171
|
|
142
172
|
Song.tagged_with('', any: true)
|
143
|
-
|
173
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
144
174
|
|
145
175
|
Song.tagged_with('rock, rap', any: true, on: ['genres'])
|
146
|
-
|
176
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
147
177
|
|
148
178
|
Song.without_all_tags('top')
|
149
|
-
|
179
|
+
#=> []
|
150
180
|
|
151
181
|
Song.tagged_with('top, 1990', exclude: true)
|
152
|
-
|
182
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
153
183
|
|
154
184
|
Song.tagged_with('', exclude: true)
|
155
|
-
|
185
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
156
186
|
|
157
187
|
Song.tagged_with('top, 1990', any: true, exclude: true)
|
158
|
-
|
188
|
+
#=> []
|
159
189
|
|
160
190
|
Song.tagged_with('1990, 1980', any: true, exclude: true)
|
161
|
-
|
191
|
+
#=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
162
192
|
|
163
193
|
Song.without_any_genres('rock, pop')
|
164
|
-
|
194
|
+
#=> []
|
165
195
|
```
|
166
196
|
|
167
197
|
## Custom delimiter
|
198
|
+
|
168
199
|
By default, a comma is used as a delimiter to create tags from a string.
|
169
200
|
You can make your own custom separator:
|
201
|
+
|
170
202
|
```ruby
|
171
203
|
Metka.config.delimiter = '|'
|
172
204
|
parsed_data = Metka::GenericParser.instance.call('cool, data|I have')
|
173
205
|
parsed_data.to_a
|
174
|
-
|
206
|
+
#=>['cool, data', 'I have']
|
175
207
|
```
|
176
208
|
|
177
209
|
## Tags with quote
|
210
|
+
|
178
211
|
```ruby
|
179
212
|
parsed_data = Metka::GenericParser.instance.call("'cool, data', code")
|
180
213
|
parsed_data.to_a
|
181
|
-
|
214
|
+
#=> ['cool, data', 'code']
|
182
215
|
```
|
183
216
|
|
184
217
|
## Custom parser
|
218
|
+
|
185
219
|
By default we use [generic_parser](lib/metka/generic_parser.rb "generic_parser")
|
186
220
|
If you want to use your custom parser you can do:
|
221
|
+
|
187
222
|
```ruby
|
188
223
|
class Song < ActiveRecord::Base
|
189
224
|
include Metka::Model(columns: %w[genres tags], parser: Your::Custom::Parser.instance)
|
190
225
|
end
|
191
226
|
```
|
227
|
+
|
192
228
|
Custom parser must be a singleton class that has a `.call` method that accepts the tag string
|
193
229
|
|
194
230
|
## Tag Cloud Strategies
|
@@ -200,29 +236,30 @@ There are several strategies to get tag statistics
|
|
200
236
|
Data about taggings is accessible via class methods of your model with `Metka::Model` attached. You can calculate a cloud for a single tagged column or multiple columns, the latter case would return to you a sum of taggings from multiple tagged columns, that are provided as arguments, for each tag present. ActiveRecord Strategy is an easiest way to implement, since it wouldn't require any additional code, but it's the slowest one on SELECT.
|
201
237
|
|
202
238
|
```ruby
|
239
|
+
|
203
240
|
class Book < ActiveRecord::Base
|
204
241
|
include Metka::Model(column: 'authors')
|
205
242
|
include Metka::Model(column: 'co_authors')
|
206
243
|
end
|
207
244
|
|
208
245
|
tag_cloud = Book.author_cloud
|
209
|
-
|
246
|
+
#=> [["L.N. Tolstoy", 3], ["F.M. Dostoevsky", 6]]
|
210
247
|
genre_cloud = Book.co_author_cloud
|
211
|
-
|
248
|
+
#=> [["A.P. Chekhov", 5], ["N.V. Gogol", 8], ["L.N. Tolstoy", 2]]
|
212
249
|
summary_cloud = Book.metka_cloud('authors', 'co_authors')
|
213
|
-
|
250
|
+
#=> [["L.N. Tolstoy", 5], ["F.M. Dostoevsky", 6], ["A.P. Chekhov", 5], ["N.V. Gogol", 8]]
|
214
251
|
```
|
215
252
|
|
216
253
|
### View Strategy
|
217
254
|
|
218
|
-
Data about taggings will be
|
255
|
+
Data about taggings will be aggregated in SQL View. Performance-wise that strategy has no benefits over ActiveRecord Strategy, but if you need to store tags aggregations in a distinct model, that's an easiest way to achieve it.
|
219
256
|
|
220
257
|
```bash
|
221
258
|
rails g metka:strategies:view --source-table-name=NAME_OF_TABLE_WITH_TAGS [--source-columns=NAME_OF_COLUMN_1 NAME_OF_COLUMN_2] [--view-name=NAME_OF_RESULTING_VIEW]
|
222
259
|
```
|
223
260
|
|
224
261
|
The code above will generate a migration that creates view with specified `NAME_OF_RESULTING_VIEW`, that would aggregate tags data from specified array of tagged columns [`NAME_OF_COLUMN_1`, `NAME_OF_COLUMN_2`, ...], that are present within specified table `NAME_OF_TABLE_WITH_TAGS`.
|
225
|
-
If `source-columns` option is not provided, then `tags` column would be used as defaults. If array of multiple values would be provided to the option, then the aggregation would be made with the tags from multiple tagged columns, so if a single tag would be found within multiple tagged columns, the resulting aggregation inside the view would have a single row for that tag with a sum of it's
|
262
|
+
If `source-columns` option is not provided, then `tags` column would be used as defaults. If array of multiple values would be provided to the option, then the aggregation would be made with the tags from multiple tagged columns, so if a single tag would be found within multiple tagged columns, the resulting aggregation inside the view would have a single row for that tag with a sum of it's occurrences across all stated tagged columns.
|
226
263
|
`view-name` option is also optional, it would just force the resulting view's name to the one of your choice. If it's not provided, then view name would be generated automatically, you could check it within generated migration.
|
227
264
|
|
228
265
|
Lets take a look at real example. We have a `notes` table with `tags` column.
|
@@ -290,7 +327,7 @@ Data about taggings will be aggregated in SQL Materialized View, that would be r
|
|
290
327
|
rails g metka:strategies:materialized_view --source-table-name=NAME_OF_TABLE_WITH_TAGS --source-columns=NAME_OF_COLUMN_1 NAME_OF_COLUMN_2 --view-name=NAME_OF_RESULTING_VIEW
|
291
328
|
```
|
292
329
|
|
293
|
-
All of the options for that
|
330
|
+
All of the options for that strategy's generation command are the same as for the View Strategy.
|
294
331
|
|
295
332
|
The migration template can be seen [here](spec/dummy/db/migrate/06_create_tagged_materialized_view_posts_materialized_view.rb "here")
|
296
333
|
|
@@ -308,15 +345,43 @@ And you can also create `TaggedNote` model to work with the view as with a Rails
|
|
308
345
|
|
309
346
|
### Table Strategy with Triggers
|
310
347
|
|
311
|
-
|
312
|
-
|
313
348
|
TBD
|
314
349
|
|
315
350
|
## Inspired by
|
351
|
+
|
316
352
|
1. [ActsAsTaggableOn](https://github.com/mbleigh/acts-as-taggable-on)
|
317
353
|
2. [ActsAsTaggableArrayOn](https://github.com/tmiyamon/acts-as-taggable-array-on)
|
318
354
|
3. [TagColumns](https://github.com/hopsoft/tag_columns)
|
319
355
|
|
356
|
+
## Migration from ActsAsTaggable
|
357
|
+
|
358
|
+
To migrate your data from `ActsAsTaggable` can be done with the following migration.
|
359
|
+
|
360
|
+
```ruby
|
361
|
+
class AddTagsToYourTable < ActiveRecord::Migration[6.0]
|
362
|
+
def change
|
363
|
+
add_column :your_table, :tags, :string, array: true
|
364
|
+
add_index :your_table, :tags, using: 'gin'
|
365
|
+
|
366
|
+
execute <<~SQL
|
367
|
+
UPDATE your_table
|
368
|
+
SET tags = tags.names
|
369
|
+
FROM (
|
370
|
+
SELECT taggings.taggable_id AS your_table_id,
|
371
|
+
array_agg(tags.name) as names
|
372
|
+
FROM tags
|
373
|
+
INNER JOIN taggings
|
374
|
+
ON tags.id = taggings.tag_id
|
375
|
+
WHERE
|
376
|
+
taggings.taggable_type = 'YouTableType'
|
377
|
+
GROUP BY taggings.taggable_id
|
378
|
+
) as tags
|
379
|
+
WHERE your_table.id = tags.your_table_id
|
380
|
+
SQL
|
381
|
+
end
|
382
|
+
end
|
383
|
+
```
|
384
|
+
|
320
385
|
## Benchmark Comparison
|
321
386
|
|
322
387
|
There are some results of benchmarking a performance of write, read and find operations for different gems, that provide solution for tagging. Keep in mind, that those results can't be used as a proof, that some solution is better than the others, since each of the benchmarked gems has their unique features. You could run the benchmarks yourself or check, what exact operations has been used for benchmarking, with [MetkaBench application](https://github.com/jetrockets/metka_bench).
|
@@ -447,9 +512,10 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
447
512
|
|
448
513
|
## Contributing
|
449
514
|
|
450
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
515
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/jetrockets/metka](https://github.com/jetrockets/metka). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
451
516
|
|
452
517
|
## Credits
|
518
|
+
|
453
519
|
![JetRockets](https://media.jetrockets.pro/jetrockets-white.png)
|
454
520
|
Metka is maintained by [JetRockets](http://www.jetrockets.ru).
|
455
521
|
|
data/forspell.dict
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# Format: one word per line. Empty lines and #-comments are supported too.
|
2
|
+
# If you want to add word with its forms, you can write 'word: example' (without quotes) on the line,
|
3
|
+
# where 'example' is existing word with the same possible forms (endings) as your word.
|
4
|
+
# Example: deduplicate: duplicate
|
5
|
+
Metka
|
6
|
+
taggings
|
7
|
+
benchmarked
|
data/gemfiles/rails52.gemfile
CHANGED
data/gemfiles/rails6.gemfile
CHANGED
data/lib/metka/generic_parser.rb
CHANGED
@@ -25,7 +25,7 @@ module Metka
|
|
25
25
|
gsub_quote_pattern!(tag_list, value, double_quote_pattern)
|
26
26
|
gsub_quote_pattern!(tag_list, value, single_quote_pattern)
|
27
27
|
|
28
|
-
tag_list.merge value.split(Regexp.new
|
28
|
+
tag_list.merge value.split(Regexp.new(delimiter)).map(&:strip).reject(&:empty?)
|
29
29
|
when Enumerable
|
30
30
|
tag_list.merge value.reject(&:empty?)
|
31
31
|
end
|
@@ -46,7 +46,7 @@ module Metka
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def single_quote_pattern
|
49
|
-
|
49
|
+
@single_quote_pattern[delimiter] ||= /(\A|#{delimiter})\s*'(.*?)'\s*(?=#{delimiter}\s*|\z)/
|
50
50
|
end
|
51
51
|
|
52
52
|
def double_quote_pattern
|
data/lib/metka/model.rb
CHANGED
@@ -35,7 +35,7 @@ module Metka
|
|
35
35
|
cols = options.delete(:on)
|
36
36
|
parsed_tag_list = parser.call(tags)
|
37
37
|
|
38
|
-
return model
|
38
|
+
return model if parsed_tag_list.empty?
|
39
39
|
|
40
40
|
request = ::Metka::QueryBuilder.new.call(model, cols, parsed_tag_list, options)
|
41
41
|
model.where(request)
|
@@ -43,10 +43,10 @@ module Metka
|
|
43
43
|
|
44
44
|
base.class_eval do
|
45
45
|
columns.each do |column|
|
46
|
-
scope "with_all_#{column}",
|
47
|
-
scope "with_any_#{column}",
|
48
|
-
scope "without_all_#{column}", ->(tags) { tagged_with(tags, on: [
|
49
|
-
scope "without_any_#{column}", ->(tags) { tagged_with(tags, on: [
|
46
|
+
scope "with_all_#{column}", ->(tags) { tagged_with(tags, on: [column]) }
|
47
|
+
scope "with_any_#{column}", ->(tags) { tagged_with(tags, on: [column], any: true) }
|
48
|
+
scope "without_all_#{column}", ->(tags) { tagged_with(tags, on: [column], exclude: true) }
|
49
|
+
scope "without_any_#{column}", ->(tags) { tagged_with(tags, on: [column], any: true, exclude: true) }
|
50
50
|
end
|
51
51
|
|
52
52
|
unless respond_to?(:tagged_with)
|
@@ -66,7 +66,7 @@ module Metka
|
|
66
66
|
prepared_unnest = columns.map { |column| "#{table_name}.#{column}" }.join(' || ')
|
67
67
|
subquery = all.select("UNNEST(#{prepared_unnest}) AS tag_name")
|
68
68
|
|
69
|
-
unscoped.from(subquery).group(:tag_name).pluck(:tag_name, 'COUNT(*) AS taggings_count')
|
69
|
+
unscoped.from(subquery).group(:tag_name).pluck(:tag_name, Arel.sql('COUNT(*) AS taggings_count'))
|
70
70
|
end
|
71
71
|
|
72
72
|
columns.each do |column|
|