miscellany 0.1.0 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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}"