miscellany 0.1.0 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,69 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Miscellany::ArbitraryPrefetch do
5
+ with_model :Post do
6
+ table do |t|
7
+ t.string :title
8
+ t.timestamps null: false
9
+ end
10
+
11
+ model do
12
+ has_many :comments
13
+ end
14
+ end
15
+
16
+ with_model :Comment do
17
+ table do |t|
18
+ t.belongs_to :post
19
+ t.string :title
20
+ t.boolean :favorite
21
+ t.timestamps null: false
22
+ end
23
+
24
+ model do
25
+ belongs_to :post
26
+ end
27
+ end
28
+
29
+ let!(:posts) { 10.times.map{|i| Post.create!(title: "Post #{i}") } }
30
+
31
+ before :each do
32
+ posts.each do |p|
33
+ 5.times{|i| p.comments.create!(title: "#{p.title} Comment #{i}") }
34
+ p.comments.last.update!(favorite: true)
35
+ end
36
+ end
37
+
38
+ it 'generally works' do
39
+ posts = Post.prefetch(favorite_comment: Comment.where(favorite: true))
40
+ expect(posts.count).to eq 10
41
+ expect(posts[0].favorite_comment).to be
42
+ end
43
+
44
+ context 'prefetch is singluar' do
45
+ it 'returns a single object' do
46
+ posts = Post.prefetch(favorite_comment: Comment.where(favorite: true))
47
+ expect(posts[0].favorite_comment).to be_a Comment
48
+ end
49
+
50
+ it 'with multiple items returns a single object' do
51
+ posts = Post.prefetch(favorite_comment: Comment.where(favorite: nil))
52
+ expect(posts[0].favorite_comment).to be_a Comment
53
+ end
54
+ end
55
+
56
+ context 'prefetch is plural' do
57
+ it 'returns an Array' do
58
+ posts = Post.prefetch(non_favorite_comments: Comment.where(favorite: nil))
59
+ expect(posts[0].non_favorite_comments).to respond_to :[]
60
+ expect(posts[0].non_favorite_comments.length).to eq 4
61
+ end
62
+
63
+ it 'with 1 item returns an Array' do
64
+ posts = Post.prefetch(non_favorite_comments: Comment.where(favorite: true))
65
+ expect(posts[0].non_favorite_comments).to respond_to :[]
66
+ expect(posts[0].non_favorite_comments.length).to eq 1
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,62 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Miscellany::ComputedColumns do
5
+ with_model :Post do
6
+ table do |t|
7
+ t.string :title
8
+ t.timestamps null: false
9
+ end
10
+
11
+ model do
12
+ has_many :comments
13
+
14
+ define_computed :favorite_comments_count, ->() {
15
+ select "COMPUTED.count AS favorite_comments_count"
16
+
17
+ query do
18
+ Comment.select(<<~SQL)
19
+ post_id AS id,
20
+ count(*) AS count
21
+ SQL
22
+ .group(:post_id)
23
+ .where(favorite: true)
24
+ end
25
+ }
26
+ end
27
+ end
28
+
29
+ with_model :Comment do
30
+ table do |t|
31
+ t.belongs_to :post
32
+ t.string :title
33
+ t.boolean :favorite
34
+ t.timestamps null: false
35
+ end
36
+
37
+ model do
38
+ belongs_to :post
39
+ end
40
+ end
41
+
42
+ let!(:posts) { 3.times.map{|i| Post.create!(title: "Post #{i}") } }
43
+
44
+ before :each do
45
+ posts.each do |p|
46
+ 5.times{|i| p.comments.create!(title: "#{p.title} Comment #{i}") }
47
+ p.comments.last.update!(favorite: true)
48
+ end
49
+ end
50
+
51
+ it 'generally works' do
52
+ ActiveRecord::Base.verbose_query_logs = true
53
+ posts = Post.with_computed(:favorite_comments_count)
54
+ expect(posts.except(:select).count).to eq 3
55
+ expect(posts[0].favorite_comments_count).to eq 1
56
+
57
+ Comment.update_all(favorite: true)
58
+ posts = Post.with_computed(:favorite_comments_count)
59
+ expect(posts.except(:select).count).to eq 3
60
+ expect(posts[0].favorite_comments_count).to eq 5
61
+ end
62
+ end
@@ -0,0 +1,295 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Miscellany::ParamValidator do
5
+ let!(:value) do
6
+ {
7
+ int_array: [1,2,3],
8
+ string_array: %w[A B C],
9
+ some_number: 2,
10
+ some_string: "Robert",
11
+ specified: nil,
12
+ array: [
13
+ {a: 2},
14
+ {a: 3},
15
+ ],
16
+ hash: {
17
+ nested_hash: {
18
+ value: 1,
19
+ }
20
+ },
21
+ }
22
+ end
23
+
24
+ # TODO transform:
25
+
26
+ def expect_valid(&blk)
27
+ result = Miscellany::ParamValidator.check(value, &blk)
28
+ expect(result.serialize).to be_nil
29
+ end
30
+
31
+ def expect_invalid(&blk)
32
+ result = Miscellany::ParamValidator.check(value, &blk)
33
+ expect(result.serialize).to be_present
34
+ end
35
+
36
+ def expect_coercion(raw, type, expectation)
37
+ result = Miscellany::ParamValidator.assert({ value: raw }, handle: ->(v){ raise 'Invalid' }) do
38
+ p :value, type: type
39
+ end
40
+ expect(result[:value]).to eq expectation
41
+ end
42
+
43
+ describe 'default:' do
44
+ it 'sets a default value' do
45
+ result = Miscellany::ParamValidator.assert({ }, handle: ->(v){ raise 'Invalid' }) do
46
+ p :value, default: 'HIA'
47
+ end
48
+ expect(result[:value]).to eq 'HIA'
49
+ end
50
+
51
+ it 'works deep' do
52
+ result = Miscellany::ParamValidator.assert({ value: {} }, handle: ->(v){ raise 'Invalid' }) do
53
+ p :value do |x|
54
+ p :value, default: 'Hia'
55
+ end
56
+ end
57
+ expect(result).to eq ({ value: { value: 'Hia' } })
58
+ end
59
+ end
60
+
61
+ describe 'specified' do
62
+ it 'passes a given value' do
63
+ expect_valid do
64
+ p :some_string, :specified
65
+ end
66
+ end
67
+
68
+ it 'passes a given nil' do
69
+ expect_valid do
70
+ p :specified, :specified
71
+ end
72
+ end
73
+
74
+ it 'fails an unspecified key' do
75
+ expect_invalid do
76
+ p :not_specified, :specified
77
+ end
78
+ end
79
+ end
80
+
81
+ describe 'present' do
82
+ it 'passes a given value' do
83
+ expect_valid do
84
+ p :some_string, :present
85
+ end
86
+ end
87
+
88
+ it 'fails a given nil' do
89
+ expect_invalid do
90
+ p :specified, :present
91
+ end
92
+ end
93
+
94
+ it 'fails an unspecified key' do
95
+ expect_invalid do
96
+ p :not_specified, :present
97
+ end
98
+ end
99
+ end
100
+
101
+ describe 'type:' do
102
+ it 'passes based on type' do
103
+ expect_valid do
104
+ p :some_number, type: Numeric
105
+ p :some_string, type: String
106
+ end
107
+ end
108
+
109
+ it 'fails based on type' do
110
+ expect_invalid do
111
+ p :some_number, type: String
112
+ p :some_string, type: Numeric
113
+ end
114
+ end
115
+
116
+ describe ':bool' do
117
+ it 'transforms booleans' do
118
+ expect_coercion('t', :bool, true)
119
+ expect_coercion('T', :bool, true)
120
+ expect_coercion('true', :bool, true)
121
+ expect_coercion('True', :bool, true)
122
+ expect_coercion('TRUE', :bool, true)
123
+ expect_coercion('YES', :bool, true)
124
+ expect_coercion('yes', :bool, true)
125
+ expect_coercion('Y', :bool, true)
126
+ expect_coercion('y', :bool, true)
127
+ expect_coercion('1', :bool, true)
128
+ expect_coercion(1, :bool, true)
129
+
130
+ expect_coercion('f', :bool, false)
131
+ expect_coercion('F', :bool, false)
132
+ expect_coercion('false', :bool, false)
133
+ expect_coercion('False', :bool, false)
134
+ expect_coercion('FALSE', :bool, false)
135
+ expect_coercion('NO', :bool, false)
136
+ expect_coercion('no', :bool, false)
137
+ expect_coercion('N', :bool, false)
138
+ expect_coercion('n', :bool, false)
139
+ expect_coercion('0', :bool, false)
140
+ expect_coercion(0, :bool, false)
141
+ end
142
+ end
143
+ end
144
+
145
+ describe 'in:' do
146
+ it 'works' do
147
+ expect_valid do
148
+ p :some_number, in: [1, 2]
149
+ end
150
+
151
+ expect_invalid do
152
+ p :some_number, in: [1, 3]
153
+ end
154
+ end
155
+
156
+ it 'works with modifiers' do
157
+ expect_valid do
158
+ p [:some_number, :some_string], one_in: [2]
159
+ p [:some_number, :some_string], one_in: ['Robert']
160
+ p [:some_number, :some_string], none_in: ['Steve', 3]
161
+ end
162
+ end
163
+ end
164
+
165
+ describe 'pattern:' do
166
+ it 'works' do
167
+ expect_valid do
168
+ p :some_string, pattern: /^Rob/
169
+ p :some_string, pattern: /^Robert$/
170
+ end
171
+ expect_invalid do
172
+ p :some_string, pattern: /^Steve$/
173
+ end
174
+ end
175
+ end
176
+
177
+ describe 'items:' do
178
+ it 'works when given a Lambda' do
179
+ expect_valid do
180
+ p :array, items: ->(*args) {
181
+ p :a, in: [2, 3]
182
+ nil
183
+ }
184
+ end
185
+ expect_invalid do
186
+ p :array, items: ->(*args) {
187
+ p :a, in: [5]
188
+ }
189
+ end
190
+ expect_invalid do
191
+ p :array, items: ->(*args) {
192
+ 'bob'
193
+ }
194
+ end
195
+ end
196
+ it 'works when given a Hash' do
197
+ # TODO
198
+ end
199
+ end
200
+
201
+ it 'supports a custom validator block' do
202
+ expect_valid do
203
+ p :some_string do |v|
204
+ nil
205
+ end
206
+ end
207
+
208
+ expect_invalid do
209
+ p :some_string do |v|
210
+ 'Bad Length'
211
+ end
212
+ end
213
+ end
214
+
215
+ describe 'modifiers' do
216
+ let!(:value) do
217
+ {
218
+ a: 1,
219
+ b: 1,
220
+ c: 1,
221
+ x: nil,
222
+ y: nil,
223
+ z: nil,
224
+ }
225
+ end
226
+
227
+ def assert_modifier(modifier, keys, exp)
228
+ blk = ->(*args) {
229
+ p keys, :"#{modifier}_present"
230
+ }
231
+ exp ? expect_valid(&blk) : expect_invalid(&blk)
232
+ end
233
+
234
+ it 'the all modifier works as expected' do
235
+ assert_modifier(:all, %i[a b c], true)
236
+ assert_modifier(:all, %i[a b z], false)
237
+ assert_modifier(:all, %i[a y z], false)
238
+ assert_modifier(:all, %i[x y z], false)
239
+ end
240
+
241
+ it 'the onem modifier works as expected' do
242
+ assert_modifier(:onem, %i[a b c], false)
243
+ assert_modifier(:onem, %i[a b z], false)
244
+ assert_modifier(:onem, %i[a y z], true)
245
+ assert_modifier(:onem, %i[x y z], true)
246
+ end
247
+
248
+ it 'the onep modifier works as expected' do
249
+ assert_modifier(:onep, %i[a b c], true)
250
+ assert_modifier(:onep, %i[a b z], true)
251
+ assert_modifier(:onep, %i[a y z], true)
252
+ assert_modifier(:onep, %i[x y z], false)
253
+ end
254
+
255
+ it 'the one modifier works as expected' do
256
+ assert_modifier(:one, %i[a b c], false)
257
+ assert_modifier(:one, %i[a b z], false)
258
+ assert_modifier(:one, %i[a y z], true)
259
+ assert_modifier(:one, %i[x y z], false)
260
+ end
261
+
262
+ it 'the none modifier works as expected' do
263
+ assert_modifier(:none, %i[a b c], false)
264
+ assert_modifier(:none, %i[a b z], false)
265
+ assert_modifier(:none, %i[a y z], false)
266
+ assert_modifier(:none, %i[x y z], true)
267
+ end
268
+
269
+ it 'aliases work' do
270
+ assert_modifier(:any, %i[a b c], true)
271
+ assert_modifier(:any, %i[a b z], true)
272
+ assert_modifier(:any, %i[a y z], true)
273
+ assert_modifier(:any, %i[x y z], false)
274
+ end
275
+ end
276
+
277
+ describe 'nesting' do
278
+ it 'works' do
279
+ expect_valid do
280
+ p :hash, :present do
281
+ p :nested_hash do
282
+ p :value, in: [1]
283
+ end
284
+ end
285
+ end
286
+ expect_invalid do
287
+ p :hash, :present do
288
+ p :nested_hash do
289
+ p :value, not_in: [1]
290
+ end
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'yaml'
5
+ require 'database_cleaner'
6
+ require 'with_model'
7
+
8
+ require 'miscellany'
9
+
10
+ FileUtils.makedirs('log')
11
+
12
+ ActiveRecord::Base.logger = Logger.new('log/test.log')
13
+ ActiveRecord::Base.logger.level = Logger::DEBUG
14
+ ActiveRecord::Migration.verbose = false
15
+
16
+ db_adapter = ENV.fetch('ADAPTER', 'sqlite3')
17
+ db_config = YAML.safe_load(File.read('spec/db/database.yml'))
18
+ ActiveRecord::Base.establish_connection(db_config[db_adapter])
19
+
20
+ RSpec.configure do |config|
21
+ config.extend WithModel
22
+
23
+ # config.order = 'random'
24
+
25
+ config.before(:suite) do
26
+ DatabaseCleaner.clean_with(:truncation)
27
+ end
28
+
29
+ config.before do
30
+ DatabaseCleaner.strategy = :transaction
31
+ end
32
+
33
+ config.before do
34
+ DatabaseCleaner.start
35
+ end
36
+
37
+ config.after do
38
+ DatabaseCleaner.clean
39
+ end
40
+ end
41
+
42
+ puts "Testing with ActiveRecord #{ActiveRecord::VERSION::STRING}"