metka 1.0.0 → 2.0.1

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: 960a0b262fa5e1dc7a4a08baf844f4f12063696133527f0ef201a4414ee498c0
4
- data.tar.gz: 2792d8b84d0ebf6c9aa197c3bbbe8d6b257f522493a2b770887105cd0d6b539d
3
+ metadata.gz: e518eab14726a263564f8bd8e2ef036be41dfae35e835339aa3236c36e064120
4
+ data.tar.gz: 66f5dcf4ecb93a301c28b900bbc2eaeb2d97e7f95d169d76409df7c47ec34134
5
5
  SHA512:
6
- metadata.gz: 4b47eb9c9b7e64609b0933b02e5704aa8e5dd5cca5ca1a9c796625942fed6b67551574cb6fe3c45190954ce8bfb56ae40bb125659751d55b5ef8f780a35beb0d
7
- data.tar.gz: fe1b4c69679173d7f8c639f3c060722c617f39f537dba1253ff1f9cd70e26a44028e8ae6676d440c2b9165691162f907a0cf4e546510c8e37194db13c59adb44
6
+ metadata.gz: a742e76cae372c76d21b4ddd0c2c7aa9e208e5756eb02f7cf1f65d0b44e58ec162a06d6dad4811340b4199803dbd176c02937b34c61248cdd6c9b0e316aba9ad
7
+ data.tar.gz: 430d84f7b3f5187b4921479eb1293ed09920f8190d025889bcc56f9f59aaac26393b74e47381788feae79805c8a3c54a16d3d1496a9f5190a90cb6bad7273226
@@ -22,7 +22,6 @@ before_install:
22
22
  - sudo service postgresql restart 11
23
23
 
24
24
  before_script:
25
- - gem update --system
26
25
  - psql -c 'CREATE ROLE travis SUPERUSER LOGIN CREATEDB;' -U postgres
27
26
  - ./bin/setup
28
27
 
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- metka (1.0.0)
5
- dry-configurable
6
- rails (~> 5.1)
4
+ metka (2.0.1)
5
+ dry-configurable (>= 0.8)
6
+ rails (>= 5.1)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
@@ -53,15 +53,17 @@ GEM
53
53
  ast (2.4.0)
54
54
  builder (3.2.3)
55
55
  coderay (1.1.2)
56
- concurrent-ruby (1.1.5)
56
+ concurrent-ruby (1.1.6)
57
57
  crass (1.0.5)
58
58
  database_cleaner (1.7.0)
59
59
  diff-lcs (1.3)
60
- dry-configurable (0.9.0)
60
+ dry-configurable (0.11.5)
61
61
  concurrent-ruby (~> 1.0)
62
62
  dry-core (~> 0.4, >= 0.4.7)
63
+ dry-equalizer (~> 0.2)
63
64
  dry-core (0.4.9)
64
65
  concurrent-ruby (~> 1.0)
66
+ dry-equalizer (0.3.0)
65
67
  erubi (1.9.0)
66
68
  faker (2.8.0)
67
69
  i18n (>= 1.6, < 1.8)
@@ -84,7 +86,7 @@ GEM
84
86
  mini_portile2 (2.4.0)
85
87
  minitest (5.13.0)
86
88
  nio4r (2.5.2)
87
- nokogiri (1.10.7)
89
+ nokogiri (1.10.8)
88
90
  mini_portile2 (~> 2.4.0)
89
91
  parallel (1.19.1)
90
92
  parser (2.6.5.0)
@@ -93,7 +95,7 @@ GEM
93
95
  pry (0.12.2)
94
96
  coderay (~> 1.1.0)
95
97
  method_source (~> 0.9.0)
96
- rack (2.0.7)
98
+ rack (2.0.8)
97
99
  rack-test (1.1.0)
98
100
  rack (>= 1.0, < 3)
99
101
  rails (5.1.7)
@@ -175,25 +177,25 @@ GEM
175
177
  unicode-display_width (1.6.0)
176
178
  websocket-driver (0.6.5)
177
179
  websocket-extensions (>= 0.1.0)
178
- websocket-extensions (0.1.4)
180
+ websocket-extensions (0.1.5)
179
181
 
180
182
  PLATFORMS
181
183
  ruby
182
184
 
183
185
  DEPENDENCIES
184
186
  activerecord (~> 5.1.1)
185
- ammeter
186
- bundler
187
- database_cleaner
188
- faker
187
+ ammeter (>= 1.1)
188
+ bundler (>= 1.3)
189
+ database_cleaner (>= 1.7)
190
+ faker (>= 2.8)
189
191
  jetrockets-standard (~> 1.0.1)
190
192
  metka!
191
- pg
192
- pry (~> 0.12.2)
193
- rake
194
- rspec (~> 3.9)
195
- rspec-rails (~> 3.9)
196
- timecop
193
+ pg (>= 1.1)
194
+ pry (>= 0.12.2)
195
+ rake (>= 0.8.7)
196
+ rspec (>= 3.9)
197
+ rspec-rails (>= 3.9)
198
+ timecop (>= 0.9)
197
199
 
198
200
  BUNDLED WITH
199
- 2.0.2
201
+ 2.1.4
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
+ [![Gem Version](https://badge.fury.io/rb/metka.svg)](https://badge.fury.io/rb/metka)
1
2
  [![Build Status](https://travis-ci.org/jetrockets/metka.svg?branch=master)](https://travis-ci.org/jetrockets/metka)
2
3
  [![Open Source Helpers](https://www.codetriage.com/jetrockets/metka/badges/users.svg)](https://www.codetriage.com/jetrockets/metka)
3
4
 
4
5
  # Metka
5
6
 
6
- Rails gem to manage tags with SonggreSQL array columns.
7
+ Rails gem to manage tags with PostgreSQL array columns.
7
8
 
8
9
  ## Installation
9
10
 
@@ -23,16 +24,32 @@ Or install it yourself as:
23
24
 
24
25
  ## Tag objects
25
26
 
27
+ ```bash
28
+ rails g migration CreateSongs
29
+ ```
30
+
31
+ ```ruby
32
+ class CreateSongs < ActiveRecord::Migration[5.0]
33
+ def change
34
+ create_table :songs do |t|
35
+ t.string :title
36
+ t.string :tags, array: true
37
+ t.string :genres, array: true
38
+ t.timestamps
39
+ end
40
+ end
41
+ end
42
+ ```
43
+
26
44
  ```ruby
27
45
  class Song < ActiveRecord::Base
28
- include Metka::Model(column: 'tags')
29
- include Metka::Model(column: 'genres')
46
+ include Metka::Model(columns: %w[genres tags])
30
47
  end
31
48
 
32
- @Song = Song.new(title: 'Migrate tags in Rails to SonggreSQL')
33
- @Song.tag_list = 'top, chill'
34
- @Song.genre_list = 'rock, jazz, pop'
35
- @Song.save
49
+ @song = Song.new(title: 'Migrate tags in Rails to PostgreSQL')
50
+ @song.tag_list = 'top, chill'
51
+ @song.genre_list = 'rock, jazz, pop'
52
+ @song.save
36
53
  ```
37
54
 
38
55
  ## Find tagged objects
@@ -40,7 +57,7 @@ end
40
57
  ### .with_all_#{column_name}
41
58
  ```ruby
42
59
  Song.with_all_tags('top')
43
- => [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
60
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
44
61
 
45
62
  Song.with_all_tags('top, 1990')
46
63
  => []
@@ -49,22 +66,22 @@ Song.with_all_tags('')
49
66
  => []
50
67
 
51
68
  Song.with_all_genres('rock')
52
- => [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
69
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
53
70
  ```
54
71
 
55
72
  ### .with_any_#{column_name}
56
73
  ```ruby
57
74
  Song.with_any_tags('chill')
58
- => [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
75
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
59
76
 
60
77
  Song.with_any_tags('chill, 1980')
61
- => [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
78
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
62
79
 
63
80
  Song.with_any_tags('')
64
81
  => []
65
82
 
66
83
  Song.with_any_genres('rock, rap')
67
- => [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
84
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
68
85
  ```
69
86
  ### .without_all_#{column_name}
70
87
  ```ruby
@@ -72,13 +89,13 @@ Song.without_all_tags('top')
72
89
  => []
73
90
 
74
91
  Song.without_all_tags('top, 1990')
75
- => [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
92
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
76
93
 
77
94
  Song.without_all_tags('')
78
- => [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
95
+ => []
79
96
 
80
97
  Song.without_all_genres('rock, pop')
81
- => [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
98
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
82
99
 
83
100
  Song.without_all_genres('rock')
84
101
  => []
@@ -90,23 +107,71 @@ Song.without_any_tags('top, 1990')
90
107
  => []
91
108
 
92
109
  Song.without_any_tags('1990, 1980')
93
- => [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
110
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
94
111
 
95
112
  Song.without_any_genres('rock, pop')
96
113
  => []
97
114
 
98
115
  Song.without_any_genres('')
99
- => [#<Song id: 1, title: 'Migrate tags in Rails to SonggreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
116
+ => []
117
+ ```
118
+
119
+ ### .tagged_with
120
+ ```ruby
121
+ Song.tagged_with('top')
122
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
123
+
124
+ Song.tagged_with('top, 1990')
125
+ => []
126
+
127
+ Song.tagged_with('')
128
+ => []
129
+
130
+ Song.tagged_with('rock')
131
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
132
+
133
+ Song.tagged_with('rock', join_operator: Metka::And)
134
+ => []
135
+
136
+ Song.tagged_with('chill', any: true)
137
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
138
+
139
+ Song.tagged_with('chill, 1980', any: true)
140
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
141
+
142
+ Song.tagged_with('', any: true)
143
+ => []
144
+
145
+ Song.tagged_with('rock, rap', any: true, on: ['genres'])
146
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
147
+
148
+ Song.without_all_tags('top')
149
+ => []
150
+
151
+ Song.tagged_with('top, 1990', exclude: true)
152
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
153
+
154
+ Song.tagged_with('', exclude: true)
155
+ => []
156
+
157
+ Song.tagged_with('top, 1990', any: true, exclude: true)
158
+ => []
159
+
160
+ Song.tagged_with('1990, 1980', any: true, exclude: true)
161
+ => [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
162
+
163
+ Song.without_any_genres('rock, pop')
164
+ => []
100
165
  ```
101
166
 
102
167
  ## Custom delimiter
103
168
  By default, a comma is used as a delimiter to create tags from a string.
104
169
  You can make your own custom separator:
105
170
  ```ruby
106
- Metka.config.delimiter = [',', ' ', '\|']
171
+ Metka.config.delimiter = '|'
107
172
  parsed_data = Metka::GenericParser.instance.call('cool, data|I have')
108
173
  parsed_data.to_a
109
- =>['cool', 'data', 'I', 'have']
174
+ =>['cool, data', 'I have']
110
175
  ```
111
176
 
112
177
  ## Tags with quote
@@ -118,11 +183,10 @@ parsed_data.to_a
118
183
 
119
184
  ## Custom parser
120
185
  By default we use [generic_parser](lib/metka/generic_parser.rb "generic_parser")
121
- If you want use your custom parser you can do:
186
+ If you want to use your custom parser you can do:
122
187
  ```ruby
123
188
  class Song < ActiveRecord::Base
124
- include Metka::Model(column: 'tags', parser: Your::Custom::Parser.instance)
125
- include Metka::Model(column: 'genres')
189
+ include Metka::Model(columns: %w[genres tags], parser: Your::Custom::Parser.instance)
126
190
  end
127
191
  ```
128
192
  Custom parser must be a singleton class that has a `.call` method that accepts the tag string
@@ -131,15 +195,35 @@ Custom parser must be a singleton class that has a `.call` method that accepts t
131
195
 
132
196
  There are several strategies to get tag statistics
133
197
 
198
+ ### ActiveRecord Strategy (Default)
199
+
200
+ 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
+
202
+ ```ruby
203
+ class Book < ActiveRecord::Base
204
+ include Metka::Model(column: 'authors')
205
+ include Metka::Model(column: 'co_authors')
206
+ end
207
+
208
+ tag_cloud = Book.author_cloud
209
+ => [["L.N. Tolstoy", 3], ["F.M. Dostoevsky", 6]]
210
+ genre_cloud = Book.co_author_cloud
211
+ => [["A.P. Chekhov", 5], ["N.V. Gogol", 8], ["L.N. Tolstoy", 2]]
212
+ summary_cloud = Book.metka_cloud('authors', 'co_authors')
213
+ => [["L.N. Tolstoy", 5], ["F.M. Dostoevsky", 6], ["A.P. Chekhov", 5], ["N.V. Gogol", 8]]
214
+ ```
215
+
134
216
  ### View Strategy
135
217
 
136
- Data about taggings will be agregated in SQL View. The easiest way to implement but the most slow on SELECT.
218
+ Data about taggings will be agregated 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.
137
219
 
138
220
  ```bash
139
- rails g metka:strategies:view --source-table-name=NAME_OF_TABLE_WITH_TAGS
221
+ 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]
140
222
  ```
141
223
 
142
- The code above will generate a migration that creates view to store aggregated data about tag in `NAME_OF_TABLE_WITH_TAGS` table.
224
+ 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 occurences across all stated tagged columns.
226
+ `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.
143
227
 
144
228
  Lets take a look at real example. We have a `notes` table with `tags` column.
145
229
 
@@ -163,15 +247,18 @@ The result would be:
163
247
  class CreateTaggedNotesView < ActiveRecord::Migration[5.0]
164
248
  def up
165
249
  execute <<-SQL
166
- CREATE OR REPLACE VIEW tagged_notes AS
167
-
168
- SELECT UNNEST
169
- ( tags ) AS tag_name,
170
- COUNT ( * ) AS taggings_count
171
- FROM
172
- notes
173
- GROUP BY
174
- name;
250
+ CREATE OR REPLACE VIEW tagged_notes AS
251
+ SELECT
252
+ tag_name,
253
+ COUNT ( * ) AS taggings_count
254
+ FROM (
255
+ SELECT UNNEST
256
+ ( tags ) AS tag_name
257
+ FROM
258
+ view_posts
259
+ ) subquery
260
+ GROUP BY
261
+ tag_name;
175
262
  SQL
176
263
  end
177
264
 
@@ -197,33 +284,27 @@ Now you can create `TaggedNote` model and work with the view like you usually do
197
284
 
198
285
  ### Materialized View Strategy
199
286
 
200
- Similar to the strategy above, but the view will be Materialized and refreshed with the trigger
287
+ Data about taggings will be aggregated in SQL Materialized View, that would be refreshed with the trigger on each change of the tagged column's data. Except for the another type of view being used, that strategy behaves the same way, as a View Strategy above.
201
288
 
202
289
  ```bash
203
- rails g metka:strategies:materialized_view --source-table-name=NAME_OF_TABLE_WITH_TAGS
290
+ 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
204
291
  ```
205
292
 
206
- The code above will generate a migration that creates view to store aggregated data about tag in `NAME_OF_TABLE_WITH_TAGS` table.
207
-
208
- Lets take a look at real example. We have a `notes` table with `tags` column.
209
-
210
- | Column | Type | Default |
211
- |--------|---------------------|-----------------------------------|
212
- | id | integer | nextval('notes_id_seq'::regclass) |
213
- | body | text | |
214
- | tags | character varying[] | '{}'::character varying[] |
215
-
216
- Now lets generate a migration.
293
+ All of the options for that stategy's generation command are the same as for the View Strategy.
217
294
 
218
- ```bash
219
- rails g metka:strategies:materialized_view --source-table-name=notes
220
- ```
295
+ The migration template can be seen [here](spec/dummy/db/migrate/06_create_tagged_materialized_view_posts_materialized_view.rb "here")
221
296
 
222
- The migration code you can see [here](spec/dummy/db/migrate/05_create_tagged_materialized_view_Songs_materialized_view.rb "here")
297
+ With the same `notes` table with `tags` column the resulting view would have the same two columns
223
298
 
224
- Now lets take a look at `tagged_notes` materialized view.
299
+ | tag_name | taggings_count |
300
+ |----------|----------------|
301
+ | Ruby | 124056 |
302
+ | React | 30632 |
303
+ | Rails | 28696 |
304
+ | Crystal | 6566 |
305
+ | Elixir | 3475 |
225
306
 
226
- Now you can create `TaggedNote` model and work with the view like you usually do with Rails models.
307
+ And you can also create `TaggedNote` model to work with the view as with a Rails model.
227
308
 
228
309
  ### Table Strategy with Triggers
229
310
 
@@ -236,6 +317,128 @@ TBD
236
317
  2. [ActsAsTaggableArrayOn](https://github.com/tmiyamon/acts-as-taggable-array-on)
237
318
  3. [TagColumns](https://github.com/hopsoft/tag_columns)
238
319
 
320
+ ## Benchmark Comparison
321
+
322
+ 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).
323
+
324
+ ```bash
325
+ $ rake bench:all
326
+ Deleted all MetkaSong
327
+ Deleted all ActsAsTaggableOn::Tagging
328
+ Deleted all ActsAsTaggableOn::Tag
329
+ Deleted all ActsAsTaggableSong
330
+ Deleted all ActsAsTaggableArraySong
331
+ Deleted all TagColumnsSong
332
+ Finished to clean
333
+
334
+ ###################################################################
335
+
336
+ bench:write
337
+
338
+ Time measurements:
339
+
340
+ Rehearsal ----------------------------------------------------------
341
+ Metka: 2.192410 0.161092 2.353502 ( 2.754766)
342
+ ActsAsTaggableOn: 13.769918 0.554951 14.324869 ( 16.990127)
343
+ ActsAsTaggableOnArray: 2.150441 0.154127 2.304568 ( 2.700022)
344
+ TagColumns: 2.202647 0.156162 2.358809 ( 2.753400)
345
+ ------------------------------------------------ total: 21.341748sec
346
+
347
+ user system total real
348
+ Metka: 2.137315 0.154046 2.291361 ( 2.643363)
349
+ ActsAsTaggableOn: 11.302848 0.448674 11.751522 ( 14.019458)
350
+ ActsAsTaggableOnArray: 2.143134 0.128655 2.271789 ( 2.670797)
351
+ TagColumns: 2.133780 0.125749 2.259529 ( 2.653404)
352
+
353
+ Memory measurements:
354
+
355
+ Calculating -------------------------------------
356
+ Metka: 179.064M memsize ( 0.000 retained)
357
+ 1.689M objects ( 0.000 retained)
358
+ 50.000 strings ( 0.000 retained)
359
+ ActsAsTaggableOn: 843.949M memsize ( 0.000 retained)
360
+ 8.550M objects ( 0.000 retained)
361
+ 50.000 strings ( 0.000 retained)
362
+ ActsAsTaggableOnArray: 178.807M memsize ( 0.000 retained)
363
+ 1.684M objects ( 0.000 retained)
364
+ 50.000 strings ( 0.000 retained)
365
+ TagColumns: 180.009M memsize ( 0.000 retained)
366
+ 1.699M objects ( 0.000 retained)
367
+ 50.000 strings ( 0.000 retained)
368
+
369
+ ###################################################################
370
+
371
+ bench:read
372
+
373
+ Time measurements:
374
+
375
+ Rehearsal ----------------------------------------------------------
376
+ Metka: 0.479695 0.044399 0.524094 ( 0.590616)
377
+ ActsAsTaggableOn: 2.436328 0.140581 2.576909 ( 3.096142)
378
+ ActsAsTaggableOnArray: 0.515198 0.042127 0.557325 ( 0.623205)
379
+ TagColumns: 0.518363 0.042661 0.561024 ( 0.626968)
380
+ ------------------------------------------------- total: 4.219352sec
381
+
382
+ user system total real
383
+ Metka: 0.446751 0.041886 0.488637 ( 0.554018)
384
+ ActsAsTaggableOn: 2.395166 0.164500 2.559666 ( 3.069655)
385
+ ActsAsTaggableOnArray: 0.439608 0.041682 0.481290 ( 0.544679)
386
+ TagColumns: 0.435404 0.041623 0.477027 ( 0.540359)
387
+
388
+ Memory measurements:
389
+
390
+ Calculating -------------------------------------
391
+ Metka: 42.291M memsize ( 0.000 retained)
392
+ 388.694k objects ( 0.000 retained)
393
+ 50.000 strings ( 0.000 retained)
394
+ ActsAsTaggableOn: 178.664M memsize ( 0.000 retained)
395
+ 1.812M objects ( 0.000 retained)
396
+ 50.000 strings ( 0.000 retained)
397
+ ActsAsTaggableOnArray: 42.173M memsize ( 0.000 retained)
398
+ 383.003k objects ( 0.000 retained)
399
+ 50.000 strings ( 0.000 retained)
400
+ TagColumns: 41.948M memsize ( 0.000 retained)
401
+ 383.003k objects ( 0.000 retained)
402
+ 50.000 strings ( 0.000 retained)
403
+
404
+ ###################################################################
405
+
406
+ bench:find_by_tag
407
+
408
+ Time measurements:
409
+
410
+ Rehearsal ----------------------------------------------------------
411
+ Metka: 0.029961 0.000059 0.030020 ( 0.030052)
412
+ ActsAsTaggableOn: 0.067095 0.000068 0.067163 ( 0.067205)
413
+ ActsAsTaggableOnArray: 0.043156 0.000133 0.043289 ( 0.043440)
414
+ TagColumns: 0.056475 0.000143 0.056618 ( 0.056697)
415
+ ------------------------------------------------- total: 0.197090sec
416
+
417
+ user system total real
418
+ Metka: 0.028291 0.000019 0.028310 ( 0.028321)
419
+ ActsAsTaggableOn: 0.065925 0.000036 0.065961 ( 0.065989)
420
+ ActsAsTaggableOnArray: 0.043214 0.000079 0.043293 ( 0.043361)
421
+ TagColumns: 0.056390 0.000160 0.056550 ( 0.056666)
422
+
423
+ Memory measurements:
424
+
425
+ Calculating -------------------------------------
426
+ Metka: 4.752M memsize ( 0.000 retained)
427
+ 43.000k objects ( 0.000 retained)
428
+ 1.000 strings ( 0.000 retained)
429
+ ActsAsTaggableOn: 8.967M memsize ( 0.000 retained)
430
+ 81.002k objects ( 0.000 retained)
431
+ 9.000 strings ( 0.000 retained)
432
+ ActsAsTaggableOnArray: 5.211M memsize ( 0.000 retained)
433
+ 57.003k objects ( 0.000 retained)
434
+ 6.000 strings ( 0.000 retained)
435
+ TagColumns: 6.696M memsize ( 0.000 retained)
436
+ 94.003k objects ( 0.000 retained)
437
+ 8.000 strings ( 0.000 retained)
438
+
439
+ Finished all benchmarks
440
+ ```
441
+
239
442
  ## Development
240
443
 
241
444
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -9,10 +9,15 @@ module Metka
9
9
  class MaterializedViewGenerator < ::Rails::Generators::Base # :nodoc:
10
10
  include Rails::Generators::Migration
11
11
 
12
+ DEFAULT_SOURCE_COLUMNS = ['tags'].freeze
13
+
12
14
  desc <<~LONGDESC
13
15
  Generates migration to implement view strategy for Metka
14
16
 
15
- > $ rails g metka:strategies:materialized_view --source-table-name=NAME_OF_TABLE_WITH_TAGS
17
+ > $ rails g metka:strategies:materialized_view \
18
+ --source-table-name=NAME_OF_TABLE_WITH_TAGS \
19
+ --source-columns=NAME_OF_TAGGED_COLUMN_1 NAME_OF_TAGGED_COLUMN_2 \
20
+ --view-name=NAME_OF_VIEW
16
21
  LONGDESC
17
22
 
18
23
  source_root File.expand_path('templates', __dir__)
@@ -20,8 +25,11 @@ module Metka
20
25
  class_option :source_table_name, type: :string, required: true,
21
26
  desc: 'Name of the table that has a column with tags'
22
27
 
23
- class_option :source_column_name, type: :string, default: 'tags',
24
- desc: 'Name of the column with stored tags'
28
+ class_option :source_columns, type: :array, default: DEFAULT_SOURCE_COLUMNS,
29
+ desc: 'List of the tagged columns names'
30
+
31
+ class_option :view_name, type: :string,
32
+ desc: 'Custom name for the resulting view'
25
33
 
26
34
  def generate_migration
27
35
  migration_template 'migration.rb.erb', "db/migrate/#{migration_name}.rb"
@@ -32,12 +40,19 @@ module Metka
32
40
  options[:source_table_name]
33
41
  end
34
42
 
35
- def source_column_name
36
- options[:source_column_name]
43
+ def source_columns
44
+ options[:source_columns]
45
+ end
46
+
47
+ def source_columns_names
48
+ source_columns.join('_and_')
37
49
  end
38
50
 
39
51
  def view_name
40
- "tagged_#{source_table_name}"
52
+ return options[:view_name] if options[:view_name]
53
+
54
+ columns_sequence = source_columns == DEFAULT_SOURCE_COLUMNS ? nil : "_with_#{source_columns_names}"
55
+ "tagged#{columns_sequence}_#{source_table_name}"
41
56
  end
42
57
 
43
58
  def migration_name
@@ -5,15 +5,18 @@ class <%= @migration_class_name %> < ActiveRecord::Migration<%= ActiveRecord::VE
5
5
  execute <<-SQL
6
6
  CREATE OR REPLACE FUNCTION metka_refresh_<%= view_name %>_materialized_view() RETURNS trigger LANGUAGE plpgsql AS $$
7
7
  BEGIN
8
- IF TG_OP = 'INSERT' AND NEW.<%= source_column_name %> IS NOT NULL THEN
8
+ IF TG_OP = 'INSERT' AND
9
+ (<%= source_columns.map { |column| "NEW.#{column} IS NOT NULL" }.join(' OR ') %>) THEN
9
10
  REFRESH MATERIALIZED VIEW CONCURRENTLY <%= view_name %>;
10
11
  END IF;
11
12
 
12
- IF TG_OP = 'UPDATE' AND OLD.<%= source_column_name %> != NEW.<%= source_column_name %> THEN
13
+ IF TG_OP = 'UPDATE' AND
14
+ (<%= source_columns.map { |column| "OLD.#{column} IS DISTINCT FROM NEW.#{column}" }.join(' OR ') %>) THEN
13
15
  REFRESH MATERIALIZED VIEW CONCURRENTLY <%= view_name %>;
14
16
  END IF;
15
17
 
16
- IF TG_OP = 'DELETE' AND OLD.<%= source_column_name %> IS NOT NULL THEN
18
+ IF TG_OP = 'DELETE' AND
19
+ (<%= source_columns.map { |column| "OLD.#{column} IS NOT NULL" }.join(' OR ') %>) THEN
17
20
  REFRESH MATERIALIZED VIEW CONCURRENTLY <%= view_name %>;
18
21
  END IF;
19
22
  RETURN NEW;
@@ -21,17 +24,21 @@ class <%= @migration_class_name %> < ActiveRecord::Migration<%= ActiveRecord::VE
21
24
 
22
25
  DROP MATERIALIZED VIEW IF EXISTS <%= view_name %>;
23
26
  CREATE MATERIALIZED VIEW <%= view_name %> AS
24
- SELECT UNNEST
25
- ( <%= source_column_name %> ) AS <%= source_column_name.singularize %>_name,
26
- COUNT ( * ) AS taggings_count
27
- FROM
28
- <%= source_table_name %>
27
+ SELECT
28
+ tag_name,
29
+ COUNT(*) AS taggings_count
30
+ FROM (
31
+ SELECT UNNEST
32
+ (<%= source_columns.join(' || ') %>) AS tag_name
33
+ FROM
34
+ <%= source_table_name %>
35
+ ) subquery
29
36
  GROUP BY
30
- <%= source_column_name.singularize %>_name;
37
+ tag_name;
31
38
 
32
- CREATE UNIQUE INDEX idx_<%= source_table_name %>_<%= source_column_name %> ON <%= view_name %>(<%= source_column_name.singularize %>_name);
39
+ CREATE UNIQUE INDEX idx_<%= source_table_name %>_<%= source_columns_names %> ON <%= view_name %>(tag_name);
33
40
 
34
- CREATE TRIGGER metka_on_<%= source_table_name %>
41
+ CREATE TRIGGER metka_on_<%= source_table_name %>_<%= source_columns_names %>
35
42
  AFTER UPDATE OR INSERT OR DELETE ON <%= source_table_name %> FOR EACH ROW
36
43
  EXECUTE PROCEDURE metka_refresh_<%= view_name %>_materialized_view();
37
44
  SQL
@@ -39,7 +46,7 @@ class <%= @migration_class_name %> < ActiveRecord::Migration<%= ActiveRecord::VE
39
46
 
40
47
  def down
41
48
  execute <<-SQL
42
- DROP TRIGGER IF EXISTS metka_on_<%= source_table_name %> ON <%= source_table_name %>;
49
+ DROP TRIGGER IF EXISTS metka_on_<%= source_table_name %>_<%= source_columns_names %> ON <%= source_table_name %>;
43
50
  DROP FUNCTION IF EXISTS metka_refresh_<%= view_name %>_materialized_view;
44
51
  DROP MATERIALIZED VIEW IF EXISTS <%= view_name %>;
45
52
  SQL
@@ -4,14 +4,17 @@ class <%= @migration_class_name %> < ActiveRecord::Migration<%= ActiveRecord::VE
4
4
  def up
5
5
  execute <<-SQL
6
6
  CREATE OR REPLACE VIEW <%= view_name %> AS
7
-
8
- SELECT UNNEST
9
- ( <%= source_column_name %> ) AS <%= source_column_name.singularize %>_name,
10
- COUNT ( * ) AS taggings_count
11
- FROM
12
- <%= source_table_name %>
13
- GROUP BY
14
- <%= source_column_name.singularize %>_name;
7
+ SELECT
8
+ tag_name,
9
+ COUNT(*) AS taggings_count
10
+ FROM (
11
+ SELECT UNNEST
12
+ (<%= source_columns.join(' || ') %>) AS tag_name
13
+ FROM
14
+ <%= source_table_name %>
15
+ ) subquery
16
+ GROUP BY
17
+ tag_name;
15
18
  SQL
16
19
  end
17
20
 
@@ -9,6 +9,8 @@ module Metka
9
9
  class ViewGenerator < ::Rails::Generators::Base # :nodoc:
10
10
  include Rails::Generators::Migration
11
11
 
12
+ DEFAULT_SOURCE_COLUMNS = ['tags'].freeze
13
+
12
14
  desc <<~LONGDESC
13
15
  Generates migration to implement view strategy for Metka
14
16
 
@@ -20,8 +22,11 @@ module Metka
20
22
  class_option :source_table_name, type: :string, required: true,
21
23
  desc: 'Name of the table that has a column with tags'
22
24
 
23
- class_option :source_column_name, type: :string, default: 'tags',
24
- desc: 'Name of the column with stored tags'
25
+ class_option :source_columns, type: :array, default: DEFAULT_SOURCE_COLUMNS,
26
+ desc: 'List of the tagged columns names'
27
+
28
+ class_option :view_name, type: :string,
29
+ desc: 'Custom name for the resulting view'
25
30
 
26
31
  def generate_migration
27
32
  migration_template 'migration.rb.erb', "db/migrate/#{migration_name}.rb"
@@ -32,12 +37,19 @@ module Metka
32
37
  options[:source_table_name]
33
38
  end
34
39
 
35
- def source_column_name
36
- options[:source_column_name]
40
+ def source_columns
41
+ options[:source_columns]
42
+ end
43
+
44
+ def source_columns_names
45
+ source_columns.join('_and_')
37
46
  end
38
47
 
39
48
  def view_name
40
- "tagged_#{source_table_name}"
49
+ return options[:view_name] if options[:view_name]
50
+
51
+ columns_sequence = source_columns == DEFAULT_SOURCE_COLUMNS ? nil : "_with_#{source_columns_names}"
52
+ "tagged#{columns_sequence}_#{source_table_name}"
41
53
  end
42
54
 
43
55
  def migration_name
@@ -16,5 +16,5 @@ module Metka
16
16
  extend Dry::Configurable
17
17
 
18
18
  setting :parser, Metka::GenericParser
19
- setting :delimiter, ','
19
+ setting :delimiter, ',', reader: true
20
20
  end
@@ -12,6 +12,11 @@ module Metka
12
12
  class GenericParser
13
13
  include Singleton
14
14
 
15
+ def initialize
16
+ @single_quote_pattern ||= {}
17
+ @double_quote_pattern ||= {}
18
+ end
19
+
15
20
  def call(value)
16
21
  TagList.new.tap do |tag_list|
17
22
  case value
@@ -20,7 +25,7 @@ module Metka
20
25
  gsub_quote_pattern!(tag_list, value, double_quote_pattern)
21
26
  gsub_quote_pattern!(tag_list, value, single_quote_pattern)
22
27
 
23
- tag_list.merge value.split(Regexp.new joined_delimiter).map(&:strip).reject(&:empty?)
28
+ tag_list.merge value.split(Regexp.new delimiter).map(&:strip).reject(&:empty?)
24
29
  when Enumerable
25
30
  tag_list.merge value.reject(&:empty?)
26
31
  end
@@ -36,17 +41,16 @@ module Metka
36
41
  }
37
42
  end
38
43
 
39
- def joined_delimiter
40
- delimeter = Metka.config.delimiter
41
- delimeter.is_a?(Array) ? delimeter.join('|') : delimeter
44
+ def delimiter
45
+ Metka.delimiter
42
46
  end
43
47
 
44
48
  def single_quote_pattern
45
- /(\A|#{joined_delimiter})\s*'(.*?)'\s*(?=#{joined_delimiter}\s*|\z)/
49
+ @single_quote_pattern[delimiter] ||= /(\A|#{delimiter})\s*'(.*?)'\s*(?=#{delimiter}\s*|\z)/
46
50
  end
47
51
 
48
52
  def double_quote_pattern
49
- /(\A|#{joined_delimiter})\s*"(.*?)"\s*(?=#{joined_delimiter}\s*|\z)/
53
+ @double_quote_pattern[delimiter] ||= /(\A|#{delimiter})\s*"(.*?)"\s*(?=#{delimiter}\s*|\z)/
50
54
  end
51
55
  end
52
56
  end
@@ -1,47 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'arel'
4
+
3
5
  module Metka
4
- def self.Model(column:, **options)
5
- Metka::Model.new(column: column, **options)
6
+ OR = Arel::Nodes::Or
7
+ AND = Arel::Nodes::And
8
+
9
+ def self.Model(column: nil, columns: nil, **options)
10
+ columns = [column, *columns].uniq.compact
11
+ raise ArgumentError, 'Columns not specified' unless columns.present?
12
+
13
+ Metka::Model.new(columns: columns, **options)
6
14
  end
7
15
 
8
16
  class Model < Module
9
- def initialize(column: , **options)
10
- @column = column
11
- @options = options
17
+ def initialize(columns:, **options)
18
+ @columns = columns.dup.freeze
19
+ @options = options.dup.freeze
12
20
  end
13
21
 
14
22
  def included(base)
15
- column = @column
23
+ columns = @columns
16
24
  parser = ->(tags) {
17
25
  @options[:parser] ? @options[:parser].call(tags) : Metka.config.parser.instance.call(tags)
18
26
  }
19
27
 
20
- search_by_tags = ->(model, tags, column, **options) {
28
+ # @param model [ActiveRecord::Base] model on which to execute search
29
+ # @param tags [Object] list of tags, representation depends on parser used
30
+ # @param options [Hash] options
31
+ # @option :join_operator [Metka::AND, Metka::OR]
32
+ # @option :on [Array<String>] list of column names to include in query
33
+ # @returns ViewPost::ActiveRecord_Relation
34
+ tagged_with_lambda = ->(model, tags, **options) {
35
+ cols = options.delete(:on)
21
36
  parsed_tag_list = parser.call(tags)
22
- if options[:without].present?
23
- model.where.not(::Metka::QueryBuilder.new.call(model, column, parsed_tag_list, options))
24
- else
25
- return model.none if parsed_tag_list.empty?
26
- model.where(::Metka::QueryBuilder.new.call(model, column, parsed_tag_list, options))
27
- end
37
+
38
+ return model.none if parsed_tag_list.empty?
39
+
40
+ request = ::Metka::QueryBuilder.new.call(model, cols, parsed_tag_list, options)
41
+ model.where(request)
28
42
  }
29
43
 
30
44
  base.class_eval do
31
- scope "with_all_#{column}", ->(tags) { search_by_tags.call(self, tags, column) }
32
- scope "with_any_#{column}", ->(tags) { search_by_tags.call(self, tags, column, { any: true }) }
33
- scope "without_all_#{column}", ->(tags) { search_by_tags.call(self, tags, column, { exclude_all: true, without: true }) }
34
- scope "without_any_#{column}", ->(tags) { search_by_tags.call(self, tags, column, { exclude_any: true, without: true }) }
45
+ columns.each do |column|
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
+ end
51
+
52
+ unless respond_to?(:tagged_with)
53
+ scope :tagged_with, ->(tags = '', options = {}) {
54
+ options[:join_operator] ||= ::Metka::OR
55
+ options = {any: false}.merge(options)
56
+ options[:on] ||= columns
57
+
58
+ tagged_with_lambda.call(self, tags, **options)
59
+ }
60
+ end
35
61
  end
36
62
 
37
- base.define_method(column.singularize + '_list=') do |v|
38
- self.write_attribute(column, parser.call(v).to_a)
39
- self.write_attribute(column, nil) if self.send(column).empty?
63
+ base.define_singleton_method :metka_cloud do |*columns|
64
+ return [] if columns.blank?
65
+
66
+ prepared_unnest = columns.map { |column| "#{table_name}.#{column}" }.join(' || ')
67
+ subquery = all.select("UNNEST(#{prepared_unnest}) AS tag_name")
68
+
69
+ unscoped.from(subquery).group(:tag_name).pluck(:tag_name, 'COUNT(*) AS taggings_count')
40
70
  end
41
71
 
42
- base.define_method(column.singularize + '_list') do
43
- parser.call(self.send(column))
72
+ columns.each do |column|
73
+ base.define_method(column.singularize + '_list=') do |v|
74
+ write_attribute(column, parser.call(v).to_a)
75
+ write_attribute(column, nil) if send(column).empty?
76
+ end
77
+
78
+ base.define_method(column.singularize + '_list') do
79
+ parser.call(send(column))
80
+ end
81
+
82
+ base.define_singleton_method :"#{column.singularize}_cloud" do
83
+ metka_cloud(column)
84
+ end
44
85
  end
45
86
  end
46
87
  end
47
- end
88
+ end
@@ -1,23 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'arel'
3
4
  require_relative 'query_builder/base_query'
4
- require_relative 'query_builder/exclude_all_tags_query'
5
- require_relative 'query_builder/exclude_any_tags_query'
6
5
  require_relative 'query_builder/any_tags_query'
7
6
  require_relative 'query_builder/all_tags_query'
8
7
 
9
8
  module Metka
10
9
  class QueryBuilder
11
- def call(taggable_model, column, tag_list, options)
12
- if options[:exclude_all].present?
13
- ExcludeAllTagsQuery.instance.call(taggable_model, column, tag_list)
14
- elsif options[:exclude_any].present?
15
- ExcludeAnyTagsQuery.instance.call(taggable_model, column, tag_list)
16
- elsif options[:any].present?
17
- AnyTagsQuery.instance.call(taggable_model, column, tag_list)
10
+ def call(model, columns, tags, options)
11
+ strategy = options_to_strategy(options)
12
+
13
+ query = join(options[:join_operator]) do
14
+ columns.map do |column|
15
+ build_query(strategy, model, column, tags)
16
+ end
17
+ end
18
+
19
+ if options[:exclude].present?
20
+ Arel::Nodes::Not.new(query)
21
+ else
22
+ query
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def options_to_strategy options
29
+ if options[:any].present?
30
+ AnyTagsQuery
18
31
  else
19
- AllTagsQuery.instance.call(taggable_model, column, tag_list)
32
+ AllTagsQuery
33
+ end
34
+ end
35
+
36
+ def join(operator, &block)
37
+ nodes = block.call
38
+
39
+ if operator == ::Metka::AND
40
+ join_and(nodes)
41
+ elsif operator == ::Metka::OR
42
+ join_or(nodes)
43
+ end
44
+ end
45
+
46
+ # @param nodes [Array<Arel::Node>, Arel::Node]
47
+ # @return [Arel::Node]
48
+ def join_or(nodes)
49
+ case nodes
50
+ when ::Arel::Node
51
+ nodes
52
+ when Array
53
+ l, *r = nodes
54
+ return l if r.empty?
55
+
56
+ l.or(join_or(r))
20
57
  end
21
58
  end
59
+
60
+ def join_and(queries)
61
+ Arel::Nodes::And.new(queries)
62
+ end
63
+
64
+ def build_query(strategy, model, column, tags)
65
+ strategy.instance.call(model, column, tags)
66
+ end
22
67
  end
23
68
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Metka
4
- VERSION = '1.0.0'
4
+ VERSION = '2.0.1'
5
5
  end
@@ -24,18 +24,19 @@ Gem::Specification.new do |spec|
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ['lib']
26
26
 
27
- spec.add_dependency 'dry-configurable'
28
- spec.add_dependency 'rails', '~> 5.1'
27
+ spec.add_dependency 'dry-configurable', '>= 0.8'
28
+ spec.add_dependency 'rails', '>= 5.1'
29
29
 
30
- spec.add_development_dependency 'ammeter'
31
- spec.add_development_dependency 'pry', '~> 0.12.2'
32
- spec.add_development_dependency 'bundler'
33
- spec.add_development_dependency 'faker'
30
+ spec.add_development_dependency 'ammeter', '>= 1.1'
31
+ spec.add_development_dependency 'pry', '>= 0.12.2'
32
+ spec.add_development_dependency 'bundler', '>= 1.3'
33
+ spec.add_development_dependency 'faker', '>= 2.8'
34
34
  spec.add_development_dependency 'jetrockets-standard', '~> 1.0.1'
35
- spec.add_development_dependency 'pg'
36
- spec.add_development_dependency 'rake'
37
- spec.add_development_dependency 'rspec', '~> 3.9'
38
- spec.add_development_dependency 'rspec-rails', '~> 3.9'
39
- spec.add_development_dependency 'timecop'
40
- spec.add_development_dependency 'database_cleaner'
35
+ spec.add_development_dependency 'pg', '>= 1.1'
36
+ spec.add_development_dependency 'rake', '>= 0.8.7'
37
+ spec.add_development_dependency 'rspec', '>= 3.9'
38
+ spec.add_development_dependency 'rspec-rails', '>= 3.9'
39
+ spec.add_development_dependency 'timecop', '>= 0.9'
40
+ spec.add_development_dependency 'database_cleaner', '>= 1.7'
41
+ spec.required_ruby_version = '>= 2.5'
41
42
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metka
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Alexandrov
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-12-13 00:00:00.000000000 Z
12
+ date: 2020-06-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dry-configurable
@@ -17,26 +17,26 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '0'
20
+ version: '0.8'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '0'
27
+ version: '0.8'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: rails
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - "~>"
32
+ - - ">="
33
33
  - !ruby/object:Gem::Version
34
34
  version: '5.1'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - "~>"
39
+ - - ">="
40
40
  - !ruby/object:Gem::Version
41
41
  version: '5.1'
42
42
  - !ruby/object:Gem::Dependency
@@ -45,26 +45,26 @@ dependencies:
45
45
  requirements:
46
46
  - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: '0'
48
+ version: '1.1'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - ">="
54
54
  - !ruby/object:Gem::Version
55
- version: '0'
55
+ version: '1.1'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: pry
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - "~>"
60
+ - - ">="
61
61
  - !ruby/object:Gem::Version
62
62
  version: 0.12.2
63
63
  type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - "~>"
67
+ - - ">="
68
68
  - !ruby/object:Gem::Version
69
69
  version: 0.12.2
70
70
  - !ruby/object:Gem::Dependency
@@ -73,28 +73,28 @@ dependencies:
73
73
  requirements:
74
74
  - - ">="
75
75
  - !ruby/object:Gem::Version
76
- version: '0'
76
+ version: '1.3'
77
77
  type: :development
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
- version: '0'
83
+ version: '1.3'
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: faker
86
86
  requirement: !ruby/object:Gem::Requirement
87
87
  requirements:
88
88
  - - ">="
89
89
  - !ruby/object:Gem::Version
90
- version: '0'
90
+ version: '2.8'
91
91
  type: :development
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
- version: '0'
97
+ version: '2.8'
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: jetrockets-standard
100
100
  requirement: !ruby/object:Gem::Requirement
@@ -115,54 +115,54 @@ dependencies:
115
115
  requirements:
116
116
  - - ">="
117
117
  - !ruby/object:Gem::Version
118
- version: '0'
118
+ version: '1.1'
119
119
  type: :development
120
120
  prerelease: false
121
121
  version_requirements: !ruby/object:Gem::Requirement
122
122
  requirements:
123
123
  - - ">="
124
124
  - !ruby/object:Gem::Version
125
- version: '0'
125
+ version: '1.1'
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: rake
128
128
  requirement: !ruby/object:Gem::Requirement
129
129
  requirements:
130
130
  - - ">="
131
131
  - !ruby/object:Gem::Version
132
- version: '0'
132
+ version: 0.8.7
133
133
  type: :development
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
137
  - - ">="
138
138
  - !ruby/object:Gem::Version
139
- version: '0'
139
+ version: 0.8.7
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: rspec
142
142
  requirement: !ruby/object:Gem::Requirement
143
143
  requirements:
144
- - - "~>"
144
+ - - ">="
145
145
  - !ruby/object:Gem::Version
146
146
  version: '3.9'
147
147
  type: :development
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
- - - "~>"
151
+ - - ">="
152
152
  - !ruby/object:Gem::Version
153
153
  version: '3.9'
154
154
  - !ruby/object:Gem::Dependency
155
155
  name: rspec-rails
156
156
  requirement: !ruby/object:Gem::Requirement
157
157
  requirements:
158
- - - "~>"
158
+ - - ">="
159
159
  - !ruby/object:Gem::Version
160
160
  version: '3.9'
161
161
  type: :development
162
162
  prerelease: false
163
163
  version_requirements: !ruby/object:Gem::Requirement
164
164
  requirements:
165
- - - "~>"
165
+ - - ">="
166
166
  - !ruby/object:Gem::Version
167
167
  version: '3.9'
168
168
  - !ruby/object:Gem::Dependency
@@ -171,28 +171,28 @@ dependencies:
171
171
  requirements:
172
172
  - - ">="
173
173
  - !ruby/object:Gem::Version
174
- version: '0'
174
+ version: '0.9'
175
175
  type: :development
176
176
  prerelease: false
177
177
  version_requirements: !ruby/object:Gem::Requirement
178
178
  requirements:
179
179
  - - ">="
180
180
  - !ruby/object:Gem::Version
181
- version: '0'
181
+ version: '0.9'
182
182
  - !ruby/object:Gem::Dependency
183
183
  name: database_cleaner
184
184
  requirement: !ruby/object:Gem::Requirement
185
185
  requirements:
186
186
  - - ">="
187
187
  - !ruby/object:Gem::Version
188
- version: '0'
188
+ version: '1.7'
189
189
  type: :development
190
190
  prerelease: false
191
191
  version_requirements: !ruby/object:Gem::Requirement
192
192
  requirements:
193
193
  - - ">="
194
194
  - !ruby/object:Gem::Version
195
- version: '0'
195
+ version: '1.7'
196
196
  description: Rails tagging system based on PostgreSQL arrays
197
197
  email:
198
198
  - igor.alexandrov@gmail.com
@@ -228,8 +228,6 @@ files:
228
228
  - lib/metka/query_builder/all_tags_query.rb
229
229
  - lib/metka/query_builder/any_tags_query.rb
230
230
  - lib/metka/query_builder/base_query.rb
231
- - lib/metka/query_builder/exclude_all_tags_query.rb
232
- - lib/metka/query_builder/exclude_any_tags_query.rb
233
231
  - lib/metka/tag_list.rb
234
232
  - lib/metka/version.rb
235
233
  - metka.gemspec
@@ -245,15 +243,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
245
243
  requirements:
246
244
  - - ">="
247
245
  - !ruby/object:Gem::Version
248
- version: '0'
246
+ version: '2.5'
249
247
  required_rubygems_version: !ruby/object:Gem::Requirement
250
248
  requirements:
251
249
  - - ">="
252
250
  - !ruby/object:Gem::Version
253
251
  version: '0'
254
252
  requirements: []
255
- rubyforge_project:
256
- rubygems_version: 2.7.6
253
+ rubygems_version: 3.0.6
257
254
  signing_key:
258
255
  specification_version: 4
259
256
  summary: Rails tagging system based on PostgreSQL arrays
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Metka
4
- class ExcludeAllTagsQuery< BaseQuery
5
- private
6
-
7
- def infix_operator
8
- '@>'
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Metka
4
- class ExcludeAnyTagsQuery< BaseQuery
5
- private
6
-
7
- def infix_operator
8
- '&&'
9
- end
10
- end
11
- end