metka 1.0.3 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -0
- data/.travis.yml +2 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +127 -112
- data/README.md +225 -40
- data/gemfiles/rails52.gemfile +2 -2
- data/gemfiles/rails6.gemfile +2 -2
- data/gemfiles/{rails5.gemfile → rails61.gemfile} +2 -2
- data/lib/generators/metka/strategies/materialized_view/materialized_view_generator.rb +21 -6
- data/lib/generators/metka/strategies/materialized_view/templates/migration.rb.erb +19 -12
- data/lib/generators/metka/strategies/view/templates/migration.rb.erb +11 -8
- data/lib/generators/metka/strategies/view/view_generator.rb +17 -5
- data/lib/metka.rb +1 -1
- data/lib/metka/generic_parser.rb +10 -6
- data/lib/metka/model.rb +64 -23
- data/lib/metka/query_builder.rb +57 -10
- data/lib/metka/query_builder/base_query.rb +1 -0
- data/lib/metka/version.rb +1 -1
- metadata +8 -9
- data/lib/metka/query_builder/exclude_all_tags_query.rb +0 -11
- data/lib/metka/query_builder/exclude_any_tags_query.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ea5e1ca3ea95cdb4f0beb2704afc1d92986b0a7c38aa49a70e794b2653c5764
|
4
|
+
data.tar.gz: e9994da8c7db21d1d079c5464d2ffae2bf06ee742f2a664df505f144b1d995be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c8da36cf3bec90c63888d24ac7d09dbec00219ca547df88289b3f858c25eb7f6a0ca729ee46021bfe47f7086a0cb7209a309ae95d8a88bb1b696acd0e772901
|
7
|
+
data.tar.gz: 4f7791a9be469c60ed1505d9f037653eccb0e81c1dcb18954c90daca440264eb9c0ca57067770c4d7222a86b2fca1c6a0ae1ad977becaad7d92ecda5ebf4513a
|
data/.rubocop.yml
CHANGED
@@ -19,7 +19,7 @@ Style/TrailingCommaInArrayLiteral:
|
|
19
19
|
Style/TrailingCommaInHashLiteral:
|
20
20
|
EnforcedStyleForMultiline: no_comma
|
21
21
|
|
22
|
-
Layout/
|
22
|
+
Layout/ParameterAlignment:
|
23
23
|
EnforcedStyle: with_first_parameter
|
24
24
|
|
25
25
|
# See https://github.com/rubocop-hq/rubocop/issues/4222
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.7.2
|
data/.travis.yml
CHANGED
@@ -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
|
|
@@ -34,3 +33,5 @@ matrix:
|
|
34
33
|
gemfile: gemfiles/rails52.gemfile
|
35
34
|
- rvm: 2.6.2
|
36
35
|
gemfile: gemfiles/rails6.gemfile
|
36
|
+
- rvm: 2.7.2
|
37
|
+
gemfile: gemfiles/rails61.gemfile
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,46 +1,50 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
metka (
|
4
|
+
metka (2.0.3)
|
5
5
|
dry-configurable (>= 0.8)
|
6
6
|
rails (>= 5.1)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
actioncable (5.
|
12
|
-
actionpack (= 5.
|
11
|
+
actioncable (5.2.4.4)
|
12
|
+
actionpack (= 5.2.4.4)
|
13
13
|
nio4r (~> 2.0)
|
14
|
-
websocket-driver (
|
15
|
-
actionmailer (5.
|
16
|
-
actionpack (= 5.
|
17
|
-
actionview (= 5.
|
18
|
-
activejob (= 5.
|
14
|
+
websocket-driver (>= 0.6.1)
|
15
|
+
actionmailer (5.2.4.4)
|
16
|
+
actionpack (= 5.2.4.4)
|
17
|
+
actionview (= 5.2.4.4)
|
18
|
+
activejob (= 5.2.4.4)
|
19
19
|
mail (~> 2.5, >= 2.5.4)
|
20
20
|
rails-dom-testing (~> 2.0)
|
21
|
-
actionpack (5.
|
22
|
-
actionview (= 5.
|
23
|
-
activesupport (= 5.
|
24
|
-
rack (~> 2.0)
|
21
|
+
actionpack (5.2.4.4)
|
22
|
+
actionview (= 5.2.4.4)
|
23
|
+
activesupport (= 5.2.4.4)
|
24
|
+
rack (~> 2.0, >= 2.0.8)
|
25
25
|
rack-test (>= 0.6.3)
|
26
26
|
rails-dom-testing (~> 2.0)
|
27
27
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
28
|
-
actionview (5.
|
29
|
-
activesupport (= 5.
|
28
|
+
actionview (5.2.4.4)
|
29
|
+
activesupport (= 5.2.4.4)
|
30
30
|
builder (~> 3.1)
|
31
31
|
erubi (~> 1.4)
|
32
32
|
rails-dom-testing (~> 2.0)
|
33
33
|
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
34
|
-
activejob (5.
|
35
|
-
activesupport (= 5.
|
34
|
+
activejob (5.2.4.4)
|
35
|
+
activesupport (= 5.2.4.4)
|
36
36
|
globalid (>= 0.3.6)
|
37
|
-
activemodel (5.
|
38
|
-
activesupport (= 5.
|
39
|
-
activerecord (5.
|
40
|
-
activemodel (= 5.
|
41
|
-
activesupport (= 5.
|
42
|
-
arel (
|
43
|
-
|
37
|
+
activemodel (5.2.4.4)
|
38
|
+
activesupport (= 5.2.4.4)
|
39
|
+
activerecord (5.2.4.4)
|
40
|
+
activemodel (= 5.2.4.4)
|
41
|
+
activesupport (= 5.2.4.4)
|
42
|
+
arel (>= 9.0)
|
43
|
+
activestorage (5.2.4.4)
|
44
|
+
actionpack (= 5.2.4.4)
|
45
|
+
activerecord (= 5.2.4.4)
|
46
|
+
marcel (~> 0.3.1)
|
47
|
+
activesupport (5.2.4.4)
|
44
48
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
45
49
|
i18n (>= 0.7, < 2)
|
46
50
|
minitest (~> 5.1)
|
@@ -49,141 +53,152 @@ GEM
|
|
49
53
|
activesupport (>= 3.0)
|
50
54
|
railties (>= 3.0)
|
51
55
|
rspec-rails (>= 2.2)
|
52
|
-
arel (
|
53
|
-
ast (2.4.
|
54
|
-
builder (3.2.
|
55
|
-
coderay (1.1.
|
56
|
-
concurrent-ruby (1.1.
|
57
|
-
crass (1.0.
|
58
|
-
database_cleaner (1.
|
59
|
-
diff-lcs (1.
|
60
|
-
dry-configurable (0.11.
|
56
|
+
arel (9.0.0)
|
57
|
+
ast (2.4.1)
|
58
|
+
builder (3.2.4)
|
59
|
+
coderay (1.1.3)
|
60
|
+
concurrent-ruby (1.1.7)
|
61
|
+
crass (1.0.6)
|
62
|
+
database_cleaner (1.8.5)
|
63
|
+
diff-lcs (1.4.4)
|
64
|
+
dry-configurable (0.11.6)
|
61
65
|
concurrent-ruby (~> 1.0)
|
62
66
|
dry-core (~> 0.4, >= 0.4.7)
|
63
67
|
dry-equalizer (~> 0.2)
|
64
|
-
dry-core (0.
|
68
|
+
dry-core (0.5.0)
|
65
69
|
concurrent-ruby (~> 1.0)
|
66
70
|
dry-equalizer (0.3.0)
|
67
|
-
erubi (1.
|
68
|
-
faker (2.
|
69
|
-
i18n (>= 1.6, <
|
71
|
+
erubi (1.10.0)
|
72
|
+
faker (2.15.1)
|
73
|
+
i18n (>= 1.6, < 2)
|
70
74
|
globalid (0.4.2)
|
71
75
|
activesupport (>= 4.2.0)
|
72
|
-
i18n (1.
|
76
|
+
i18n (1.8.5)
|
73
77
|
concurrent-ruby (~> 1.0)
|
74
|
-
|
75
|
-
|
76
|
-
rubocop-
|
77
|
-
|
78
|
-
|
79
|
-
loofah (2.4.0)
|
78
|
+
jetrockets-standard (1.0.7)
|
79
|
+
rubocop-rails (~> 2.8.1)
|
80
|
+
rubocop-rspec (~> 1.43.2)
|
81
|
+
standard (~> 0.6.1)
|
82
|
+
loofah (2.8.0)
|
80
83
|
crass (~> 1.0.2)
|
81
84
|
nokogiri (>= 1.5.9)
|
82
85
|
mail (2.7.1)
|
83
86
|
mini_mime (>= 0.1.1)
|
84
|
-
|
87
|
+
marcel (0.3.3)
|
88
|
+
mimemagic (~> 0.3.2)
|
89
|
+
method_source (1.0.0)
|
90
|
+
mimemagic (0.3.5)
|
85
91
|
mini_mime (1.0.2)
|
86
92
|
mini_portile2 (2.4.0)
|
87
|
-
minitest (5.
|
88
|
-
nio4r (2.5.
|
89
|
-
nokogiri (1.10.
|
93
|
+
minitest (5.14.2)
|
94
|
+
nio4r (2.5.4)
|
95
|
+
nokogiri (1.10.10)
|
90
96
|
mini_portile2 (~> 2.4.0)
|
91
|
-
parallel (1.
|
92
|
-
parser (2.
|
93
|
-
ast (~> 2.4.
|
94
|
-
pg (1.
|
95
|
-
pry (0.
|
96
|
-
coderay (~> 1.1
|
97
|
-
method_source (~>
|
98
|
-
rack (2.
|
97
|
+
parallel (1.20.1)
|
98
|
+
parser (2.7.2.0)
|
99
|
+
ast (~> 2.4.1)
|
100
|
+
pg (1.2.3)
|
101
|
+
pry (0.13.1)
|
102
|
+
coderay (~> 1.1)
|
103
|
+
method_source (~> 1.0)
|
104
|
+
rack (2.2.3)
|
99
105
|
rack-test (1.1.0)
|
100
106
|
rack (>= 1.0, < 3)
|
101
|
-
rails (5.
|
102
|
-
actioncable (= 5.
|
103
|
-
actionmailer (= 5.
|
104
|
-
actionpack (= 5.
|
105
|
-
actionview (= 5.
|
106
|
-
activejob (= 5.
|
107
|
-
activemodel (= 5.
|
108
|
-
activerecord (= 5.
|
109
|
-
|
107
|
+
rails (5.2.4.4)
|
108
|
+
actioncable (= 5.2.4.4)
|
109
|
+
actionmailer (= 5.2.4.4)
|
110
|
+
actionpack (= 5.2.4.4)
|
111
|
+
actionview (= 5.2.4.4)
|
112
|
+
activejob (= 5.2.4.4)
|
113
|
+
activemodel (= 5.2.4.4)
|
114
|
+
activerecord (= 5.2.4.4)
|
115
|
+
activestorage (= 5.2.4.4)
|
116
|
+
activesupport (= 5.2.4.4)
|
110
117
|
bundler (>= 1.3.0)
|
111
|
-
railties (= 5.
|
118
|
+
railties (= 5.2.4.4)
|
112
119
|
sprockets-rails (>= 2.0.0)
|
113
120
|
rails-dom-testing (2.0.3)
|
114
121
|
activesupport (>= 4.2.0)
|
115
122
|
nokogiri (>= 1.6)
|
116
123
|
rails-html-sanitizer (1.3.0)
|
117
124
|
loofah (~> 2.3)
|
118
|
-
railties (5.
|
119
|
-
actionpack (= 5.
|
120
|
-
activesupport (= 5.
|
125
|
+
railties (5.2.4.4)
|
126
|
+
actionpack (= 5.2.4.4)
|
127
|
+
activesupport (= 5.2.4.4)
|
121
128
|
method_source
|
122
129
|
rake (>= 0.8.7)
|
123
|
-
thor (>= 0.
|
130
|
+
thor (>= 0.19.0, < 2.0)
|
124
131
|
rainbow (3.0.0)
|
125
|
-
rake (13.0.
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
rspec-
|
130
|
-
|
131
|
-
rspec-
|
132
|
-
rspec-
|
132
|
+
rake (13.0.3)
|
133
|
+
regexp_parser (2.0.1)
|
134
|
+
rexml (3.2.4)
|
135
|
+
rspec (3.10.0)
|
136
|
+
rspec-core (~> 3.10.0)
|
137
|
+
rspec-expectations (~> 3.10.0)
|
138
|
+
rspec-mocks (~> 3.10.0)
|
139
|
+
rspec-core (3.10.0)
|
140
|
+
rspec-support (~> 3.10.0)
|
141
|
+
rspec-expectations (3.10.0)
|
133
142
|
diff-lcs (>= 1.2.0, < 2.0)
|
134
|
-
rspec-support (~> 3.
|
135
|
-
rspec-mocks (3.
|
143
|
+
rspec-support (~> 3.10.0)
|
144
|
+
rspec-mocks (3.10.0)
|
136
145
|
diff-lcs (>= 1.2.0, < 2.0)
|
137
|
-
rspec-support (~> 3.
|
138
|
-
rspec-rails (
|
139
|
-
actionpack (>=
|
140
|
-
activesupport (>=
|
141
|
-
railties (>=
|
142
|
-
rspec-core (~> 3.9
|
143
|
-
rspec-expectations (~> 3.9
|
144
|
-
rspec-mocks (~> 3.9
|
145
|
-
rspec-support (~> 3.9
|
146
|
-
rspec-support (3.
|
147
|
-
rubocop (0.
|
148
|
-
jaro_winkler (~> 1.5.1)
|
146
|
+
rspec-support (~> 3.10.0)
|
147
|
+
rspec-rails (4.0.1)
|
148
|
+
actionpack (>= 4.2)
|
149
|
+
activesupport (>= 4.2)
|
150
|
+
railties (>= 4.2)
|
151
|
+
rspec-core (~> 3.9)
|
152
|
+
rspec-expectations (~> 3.9)
|
153
|
+
rspec-mocks (~> 3.9)
|
154
|
+
rspec-support (~> 3.9)
|
155
|
+
rspec-support (3.10.0)
|
156
|
+
rubocop (0.91.1)
|
149
157
|
parallel (~> 1.10)
|
150
|
-
parser (>= 2.
|
158
|
+
parser (>= 2.7.1.1)
|
151
159
|
rainbow (>= 2.2.2, < 4.0)
|
160
|
+
regexp_parser (>= 1.7)
|
161
|
+
rexml
|
162
|
+
rubocop-ast (>= 0.4.0, < 1.0)
|
152
163
|
ruby-progressbar (~> 1.7)
|
153
|
-
unicode-display_width (>= 1.4.0, <
|
154
|
-
rubocop-
|
155
|
-
|
156
|
-
rubocop-
|
164
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
165
|
+
rubocop-ast (0.8.0)
|
166
|
+
parser (>= 2.7.1.5)
|
167
|
+
rubocop-performance (1.8.1)
|
168
|
+
rubocop (>= 0.87.0)
|
169
|
+
rubocop-ast (>= 0.4.0)
|
170
|
+
rubocop-rails (2.8.1)
|
171
|
+
activesupport (>= 4.2.0)
|
157
172
|
rack (>= 1.1)
|
158
|
-
rubocop (>= 0.
|
159
|
-
rubocop-rspec (1.
|
160
|
-
rubocop (
|
173
|
+
rubocop (>= 0.87.0)
|
174
|
+
rubocop-rspec (1.43.2)
|
175
|
+
rubocop (~> 0.87)
|
161
176
|
ruby-progressbar (1.10.1)
|
162
|
-
sprockets (4.0.
|
177
|
+
sprockets (4.0.2)
|
163
178
|
concurrent-ruby (~> 1.0)
|
164
179
|
rack (> 1, < 3)
|
165
|
-
sprockets-rails (3.2.
|
180
|
+
sprockets-rails (3.2.2)
|
166
181
|
actionpack (>= 4.0)
|
167
182
|
activesupport (>= 4.0)
|
168
183
|
sprockets (>= 3.0.0)
|
169
|
-
standard (0.
|
170
|
-
rubocop (
|
171
|
-
rubocop-performance (
|
172
|
-
thor (0.
|
184
|
+
standard (0.6.2)
|
185
|
+
rubocop (= 0.91.1)
|
186
|
+
rubocop-performance (= 1.8.1)
|
187
|
+
thor (1.0.1)
|
173
188
|
thread_safe (0.3.6)
|
174
|
-
timecop (0.9.
|
175
|
-
tzinfo (1.2.
|
189
|
+
timecop (0.9.2)
|
190
|
+
tzinfo (1.2.9)
|
176
191
|
thread_safe (~> 0.1)
|
177
|
-
unicode-display_width (1.
|
178
|
-
websocket-driver (0.
|
192
|
+
unicode-display_width (1.7.0)
|
193
|
+
websocket-driver (0.7.3)
|
179
194
|
websocket-extensions (>= 0.1.0)
|
180
|
-
websocket-extensions (0.1.
|
195
|
+
websocket-extensions (0.1.5)
|
181
196
|
|
182
197
|
PLATFORMS
|
183
198
|
ruby
|
184
199
|
|
185
200
|
DEPENDENCIES
|
186
|
-
activerecord (
|
201
|
+
activerecord (>= 5.2.4, < 6.2)
|
187
202
|
ammeter (>= 1.1)
|
188
203
|
bundler (>= 1.3)
|
189
204
|
database_cleaner (>= 1.7)
|
@@ -198,4 +213,4 @@ DEPENDENCIES
|
|
198
213
|
timecop (>= 0.9)
|
199
214
|
|
200
215
|
BUNDLED WITH
|
201
|
-
2.
|
216
|
+
2.1.4
|
data/README.md
CHANGED
@@ -43,8 +43,7 @@ end
|
|
43
43
|
|
44
44
|
```ruby
|
45
45
|
class Song < ActiveRecord::Base
|
46
|
-
include Metka::Model(
|
47
|
-
include Metka::Model(column: 'genres')
|
46
|
+
include Metka::Model(columns: %w[genres tags])
|
48
47
|
end
|
49
48
|
|
50
49
|
@song = Song.new(title: 'Migrate tags in Rails to PostgreSQL')
|
@@ -93,7 +92,7 @@ Song.without_all_tags('top, 1990')
|
|
93
92
|
=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
94
93
|
|
95
94
|
Song.without_all_tags('')
|
96
|
-
=> [
|
95
|
+
=> []
|
97
96
|
|
98
97
|
Song.without_all_genres('rock, pop')
|
99
98
|
=> [#<Song id: 1, title: 'Migrate tags in Rails to PostgreSQL', tags: ['top', 'chill'], genres: ['rock', 'jazz', 'pop']]
|
@@ -114,17 +113,65 @@ Song.without_any_genres('rock, pop')
|
|
114
113
|
=> []
|
115
114
|
|
116
115
|
Song.without_any_genres('')
|
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'])
|
117
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
|
+
=> []
|
118
165
|
```
|
119
166
|
|
120
167
|
## Custom delimiter
|
121
168
|
By default, a comma is used as a delimiter to create tags from a string.
|
122
169
|
You can make your own custom separator:
|
123
170
|
```ruby
|
124
|
-
Metka.config.delimiter =
|
171
|
+
Metka.config.delimiter = '|'
|
125
172
|
parsed_data = Metka::GenericParser.instance.call('cool, data|I have')
|
126
173
|
parsed_data.to_a
|
127
|
-
=>['cool
|
174
|
+
=>['cool, data', 'I have']
|
128
175
|
```
|
129
176
|
|
130
177
|
## Tags with quote
|
@@ -136,11 +183,10 @@ parsed_data.to_a
|
|
136
183
|
|
137
184
|
## Custom parser
|
138
185
|
By default we use [generic_parser](lib/metka/generic_parser.rb "generic_parser")
|
139
|
-
If you want use your custom parser you can do:
|
186
|
+
If you want to use your custom parser you can do:
|
140
187
|
```ruby
|
141
188
|
class Song < ActiveRecord::Base
|
142
|
-
include Metka::Model(
|
143
|
-
include Metka::Model(column: 'genres')
|
189
|
+
include Metka::Model(columns: %w[genres tags], parser: Your::Custom::Parser.instance)
|
144
190
|
end
|
145
191
|
```
|
146
192
|
Custom parser must be a singleton class that has a `.call` method that accepts the tag string
|
@@ -149,15 +195,35 @@ Custom parser must be a singleton class that has a `.call` method that accepts t
|
|
149
195
|
|
150
196
|
There are several strategies to get tag statistics
|
151
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
|
+
|
152
216
|
### View Strategy
|
153
217
|
|
154
|
-
Data about taggings will be agregated in SQL View.
|
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.
|
155
219
|
|
156
220
|
```bash
|
157
|
-
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]
|
158
222
|
```
|
159
223
|
|
160
|
-
The code above will generate a migration that creates view
|
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.
|
161
227
|
|
162
228
|
Lets take a look at real example. We have a `notes` table with `tags` column.
|
163
229
|
|
@@ -181,15 +247,18 @@ The result would be:
|
|
181
247
|
class CreateTaggedNotesView < ActiveRecord::Migration[5.0]
|
182
248
|
def up
|
183
249
|
execute <<-SQL
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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;
|
193
262
|
SQL
|
194
263
|
end
|
195
264
|
|
@@ -215,33 +284,27 @@ Now you can create `TaggedNote` model and work with the view like you usually do
|
|
215
284
|
|
216
285
|
### Materialized View Strategy
|
217
286
|
|
218
|
-
|
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.
|
219
288
|
|
220
289
|
```bash
|
221
|
-
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
|
222
291
|
```
|
223
292
|
|
224
|
-
|
225
|
-
|
226
|
-
Lets take a look at real example. We have a `notes` table with `tags` column.
|
227
|
-
|
228
|
-
| Column | Type | Default |
|
229
|
-
|--------|---------------------|-----------------------------------|
|
230
|
-
| id | integer | nextval('notes_id_seq'::regclass) |
|
231
|
-
| body | text | |
|
232
|
-
| tags | character varying[] | '{}'::character varying[] |
|
233
|
-
|
234
|
-
Now lets generate a migration.
|
293
|
+
All of the options for that stategy's generation command are the same as for the View Strategy.
|
235
294
|
|
236
|
-
|
237
|
-
rails g metka:strategies:materialized_view --source-table-name=notes
|
238
|
-
```
|
295
|
+
The migration template can be seen [here](spec/dummy/db/migrate/06_create_tagged_materialized_view_posts_materialized_view.rb "here")
|
239
296
|
|
240
|
-
|
297
|
+
With the same `notes` table with `tags` column the resulting view would have the same two columns
|
241
298
|
|
242
|
-
|
299
|
+
| tag_name | taggings_count |
|
300
|
+
|----------|----------------|
|
301
|
+
| Ruby | 124056 |
|
302
|
+
| React | 30632 |
|
303
|
+
| Rails | 28696 |
|
304
|
+
| Crystal | 6566 |
|
305
|
+
| Elixir | 3475 |
|
243
306
|
|
244
|
-
|
307
|
+
And you can also create `TaggedNote` model to work with the view as with a Rails model.
|
245
308
|
|
246
309
|
### Table Strategy with Triggers
|
247
310
|
|
@@ -254,6 +317,128 @@ TBD
|
|
254
317
|
2. [ActsAsTaggableArrayOn](https://github.com/tmiyamon/acts-as-taggable-array-on)
|
255
318
|
3. [TagColumns](https://github.com/hopsoft/tag_columns)
|
256
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
|
+
|
257
442
|
## Development
|
258
443
|
|
259
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.
|
@@ -265,7 +450,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
265
450
|
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/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.
|
266
451
|
|
267
452
|
## Credits
|
268
|
-
![JetRockets](https://jetrockets.pro/jetrockets-
|
453
|
+
![JetRockets](https://media.jetrockets.pro/jetrockets-white.png)
|
269
454
|
Metka is maintained by [JetRockets](http://www.jetrockets.ru).
|
270
455
|
|
271
456
|
## License
|