groonga-client-model 1.0.0 → 1.0.1

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/doc/text/news.md +30 -0
  3. data/groonga-client-model.gemspec +1 -1
  4. data/lib/groonga_client_model/log_subscriber.rb +1 -3
  5. data/lib/groonga_client_model/migration.rb +493 -0
  6. data/lib/groonga_client_model/migrator.rb +240 -0
  7. data/lib/groonga_client_model/railtie.rb +4 -2
  8. data/lib/groonga_client_model/railties/groonga.rake +55 -2
  9. data/lib/groonga_client_model/record.rb +39 -1
  10. data/lib/groonga_client_model/schema.rb +8 -0
  11. data/lib/groonga_client_model/schema_loader.rb +4 -9
  12. data/lib/groonga_client_model/test/groonga_server_runner.rb +19 -3
  13. data/lib/groonga_client_model/version.rb +1 -1
  14. data/lib/rails/generators/groonga_client_model/migration/templates/create_table_migration.rb +14 -0
  15. data/lib/rails/generators/groonga_client_model/migration/templates/delete_config_migration.rb +9 -0
  16. data/lib/rails/generators/groonga_client_model/migration/templates/migration.rb +12 -0
  17. data/lib/rails/generators/groonga_client_model/migration/templates/set_config_migration.rb +10 -0
  18. data/lib/rails/generators/groonga_client_model/migration_generator.rb +125 -0
  19. data/lib/rails/generators/groonga_client_model/{model/model_generator.rb → model_generator.rb} +18 -4
  20. data/test/apps/rails4/Gemfile.lock +2 -2
  21. data/test/apps/rails4/db/groonga/migrate/20170303120517_create_posts.rb +8 -0
  22. data/test/apps/rails4/db/groonga/migrate/20170303120527_create_terms.rb +7 -0
  23. data/test/apps/rails4/db/groonga/migrate/20170303120536_create_ages.rb +6 -0
  24. data/test/apps/rails4/log/development.log +22 -0
  25. data/test/apps/rails4/log/test.log +1350 -0
  26. data/test/apps/rails4/test/generators/migration_generator_test.rb +103 -0
  27. data/test/apps/rails4/test/tmp/db/groonga/migrate/20170307045825_set_config_alias_column.rb +10 -0
  28. data/test/apps/rails5/Gemfile.lock +8 -8
  29. data/test/apps/rails5/db/groonga/migrate/20170301061420_create_posts.rb +8 -0
  30. data/test/apps/rails5/db/groonga/migrate/20170303115054_create_terms.rb +7 -0
  31. data/test/apps/rails5/db/groonga/migrate/20170303115135_create_ages.rb +6 -0
  32. data/test/apps/rails5/log/development.log +350 -0
  33. data/test/apps/rails5/log/test.log +3260 -0
  34. data/test/apps/rails5/test/generators/migration_generator_test.rb +103 -0
  35. data/test/apps/rails5/test/tmp/db/groonga/migrate/20170307081511_remove_title_from_posts.rb +5 -0
  36. data/test/apps/rails5/tmp/cache/assets/sprockets/v3.0/5C/5Cws9GLWcOju_f3tIpY01qSaj7zkLAU0a2bQmpf7sS8.cache +1 -0
  37. data/test/apps/rails5/tmp/cache/assets/sprockets/v3.0/XH/XH9pWZvGgK476BpPGID5z8hjzRmGJjtzV0cHBLXhIKc.cache +1 -0
  38. data/test/unit/fixtures/migrate/20170301061420_create_posts.rb +8 -0
  39. data/test/unit/fixtures/migrate/20170303115054_create_terms.rb +7 -0
  40. data/test/unit/fixtures/migrate/20170303115135_create_ages.rb +6 -0
  41. data/test/unit/migration/test_config.rb +62 -0
  42. data/test/unit/migration/test_copy.rb +117 -0
  43. data/test/unit/migration/test_create_table.rb +528 -0
  44. data/test/unit/migration/test_exist.rb +47 -0
  45. data/test/unit/migration/test_load.rb +83 -0
  46. data/test/unit/record/test_active_model.rb +31 -0
  47. data/test/unit/record/test_readers.rb +45 -0
  48. data/test/unit/record/test_timestamps.rb +76 -0
  49. data/test/unit/record/test_validators.rb +295 -0
  50. data/test/unit/run-test.rb +1 -2
  51. data/test/unit/test_helper.rb +109 -0
  52. data/test/unit/test_load_value_generator.rb +8 -7
  53. data/test/unit/test_migrator.rb +156 -0
  54. metadata +64 -11
  55. data/test/apps/rails4/db/schema.grn +0 -9
  56. data/test/apps/rails5/db/schema.grn +0 -11
  57. data/test/unit/test_record.rb +0 -345
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a4d70ddd3492d74c3be891c28f0801bbc7f8eae
4
- data.tar.gz: 1bdfc60e0062e824047ba5ed71e3b1cdd1276b2a
3
+ metadata.gz: 8bb098148454489683ac35966f22fc18a32b8a3c
4
+ data.tar.gz: 9ab10bad50574e82563929857c3a5d4a3b209767
5
5
  SHA512:
6
- metadata.gz: fd5a08fd22a36b8f38c4e9bd40ba0a1a84e4427c0d07d1da538bbf11a014c2ff25d4bf83f5a0db9e99a2fcce19b360de149d44d2aa1c80f7157c9de57f84f520
7
- data.tar.gz: 70929c9b847839fcfb2779f21096e395403a449009e29f8d047a67826225de095fdc4d472c1fcecef27c31d1a089a76a2c4750b8a82091a003fd71adcddddf8b
6
+ metadata.gz: d5b863f329f13dc589514544397da3a4e10d9ff830fcb2a69bd6a686574fce6589642c2b132f436e95fbf70f84f1f24e65118bd03f2571a9a7a59ef1a232dd3b
7
+ data.tar.gz: 6840fa4300924d4e9566cb8f3958e68590ca9f2eb187bb3174e2a5977c6325cf771b6ec414a24fa82e834bcdce50c1697a05c5a1e450a464c94826aafc6db030
data/doc/text/news.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # NEWS
2
2
 
3
+ ## 1.0.1 - 2016-03-09
4
+
5
+ ### Improvements
6
+
7
+ * Supported migration: The following tasks are available:
8
+
9
+ * `groonga:migrate`: migrates the Groonga database to the latest schema.
10
+
11
+ * `groonga:migrate:rollback`: rollbacks the last migration.
12
+
13
+ * `groonga:migrate:redo`: rollbacks the last migration and
14
+ re-applies the last migration.
15
+
16
+ * Supported auto timestamp columns (`created_at`, `created_on`,
17
+ `updated_at` and `updated_on`) update.
18
+
19
+ * {GroongaClientModel::Record#find}: Supported record object.
20
+
21
+ * Added model generator.
22
+
23
+ * Added migration generator.
24
+
25
+ * {GroongaClientModel::Record}: Supported auto predicate method
26
+ generation for `Bool` type columns.
27
+
28
+ ### Fixes
29
+
30
+ * Fixed a bug that groonga-client-model is the default ORM even when
31
+ Active Record is enabled.
32
+
3
33
  ## 1.0.0 - 2016-02-07
4
34
 
5
35
  ### Fixes
@@ -43,7 +43,7 @@ Gem::Specification.new do |spec|
43
43
  spec.files += Dir.glob("doc/text/*")
44
44
  spec.test_files += Dir.glob("test/**/*")
45
45
 
46
- spec.add_runtime_dependency("groonga-client", ">= 0.4.1")
46
+ spec.add_runtime_dependency("groonga-client", ">= 0.4.2")
47
47
  spec.add_runtime_dependency("groonga-command-parser")
48
48
  spec.add_runtime_dependency("activemodel")
49
49
 
@@ -43,10 +43,8 @@ module GroongaClientModel
43
43
  end
44
44
  end
45
45
 
46
- reset_runtime
47
-
48
46
  def groonga(event)
49
- self.class.runtime += event.duration
47
+ self.class.runtime = (self.class.runtime || 0) + event.duration
50
48
 
51
49
  debug do
52
50
  command = event.payload[:command]
@@ -0,0 +1,493 @@
1
+ # Copyright (C) 2017 Kouhei Sutou <kou@clear-code.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License as published by the Free Software Foundation; either
6
+ # version 2.1 of the License, or (at your option) any later version.
7
+ #
8
+ # This library is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
+
17
+ require "benchmark"
18
+
19
+ module GroongaClientModel
20
+ class MigrationError < Error
21
+ end
22
+
23
+ class IrreversibleMigrationError < MigrationError
24
+ end
25
+
26
+ class IllegalMigrationNameError < MigrationError
27
+ class << self
28
+ def validate(name)
29
+ case name
30
+ when /\A[_a-z0-9]+\z/
31
+ # OK
32
+ else
33
+ raise new(name)
34
+ end
35
+ end
36
+ end
37
+
38
+ attr_reader :name
39
+ def initialize(name)
40
+ @name = name
41
+ message = "Illegal name for migration file: #{name}\n"
42
+ message << "\t(only lower case letters, numbers, and '_' allowed)."
43
+ super(message)
44
+ end
45
+ end
46
+
47
+ class Migration
48
+ attr_accessor :output
49
+ attr_reader :client
50
+
51
+ def initialize(client)
52
+ @client = client
53
+ @output = nil
54
+ @reverting = false
55
+ @pending_actions = []
56
+ end
57
+
58
+ def up
59
+ change
60
+ end
61
+
62
+ def down
63
+ revert do
64
+ change
65
+ end
66
+ end
67
+
68
+ def create_table(name,
69
+ type: nil,
70
+ key_type: nil,
71
+ tokenizer: nil,
72
+ default_tokenizer: nil,
73
+ normalizer: nil,
74
+ propose: nil)
75
+ if @reverting
76
+ @pending_actions << [:remove_table, name]
77
+ return
78
+ end
79
+
80
+ case propose
81
+ when :full_text_search
82
+ type ||= :patricia_trie
83
+ tokenizer ||= :bigram
84
+ normalizer ||= :auto
85
+ end
86
+
87
+ type = normalize_table_type(type || :array)
88
+ if type != "TABLE_NO_KEY" and key_type.nil?
89
+ key_type ||= "ShortText"
90
+ end
91
+ key_type = normalize_type(key_type)
92
+ if type != "TABLE_NO_KEY" and key_type == "ShortText"
93
+ tokenizer ||= default_tokenizer
94
+ tokenizer = normalize_tokenizer(tokenizer)
95
+ normalizer = normalize_normalizer(normalizer)
96
+ end
97
+ options = {type: type}
98
+ options[:key_type] = key_type if key_type
99
+ options[:tokenizer] = tokenizer if tokenizer
100
+ options[:normalizer] = normalizer if normalizer
101
+ report(__method__, [name, options]) do
102
+ @client.request(:table_create).
103
+ parameter(:name, name).
104
+ flags_parameter(:flags, [type]).
105
+ parameter(:key_type, key_type).
106
+ parameter(:default_tokenizer, tokenizer).
107
+ parameter(:normalizer, normalizer).
108
+ response
109
+ end
110
+
111
+ yield(CreateTableMigration.new(self, name)) if block_given?
112
+ end
113
+
114
+ def remove_table(name)
115
+ if @reverting
116
+ raise IrreversibleMigrationError, "can't revert remove_table(#{name})"
117
+ end
118
+
119
+ report(__method__, [name]) do
120
+ @client.request(:table_remove).
121
+ parameter(:name, name).
122
+ response
123
+ end
124
+ end
125
+
126
+ def copy_table(from_table_name, to_table_name)
127
+ if @reverting
128
+ message = "can't revert copy_table(#{from_table_name}, #{to_table_name})"
129
+ raise IrreversibleMigrationError, message
130
+ end
131
+
132
+ report(__method__, [from_table_name, to_table_name]) do
133
+ @client.request(:table_copy).
134
+ parameter(:from_name, from_table_name).
135
+ parameter(:to_name, to_table_name).
136
+ response
137
+ end
138
+ end
139
+
140
+ def add_column(table_name, column_name, value_type,
141
+ flags: nil,
142
+ type: nil,
143
+ sources: nil,
144
+ source: nil)
145
+ if @reverting
146
+ @pending_actions << [:remove_column, table_name, column_name]
147
+ return
148
+ end
149
+
150
+ value_type = normalize_type(value_type)
151
+ type = normalize_column_type(type || :scalar)
152
+ sources ||= source || []
153
+ flags = Array(flags) | [type]
154
+ if type == "COLUMN_INDEX"
155
+ schema = GroongaClientModel::Schema.new
156
+ case schema.tables[table_name].tokenizer
157
+ when nil, "TokenDelimit"
158
+ # do nothing
159
+ else
160
+ flags << "WITH_POSITION"
161
+ end
162
+ if sources.size > 1
163
+ flags << "WITH_SECTION"
164
+ end
165
+ end
166
+ options = {
167
+ flags: flags,
168
+ value_type: value_type,
169
+ }
170
+ options[:sources] = sources unless sources.blank?
171
+ report(__method__, [table_name, column_name, options]) do
172
+ @client.request(:column_create).
173
+ parameter(:table, table_name).
174
+ parameter(:name, column_name).
175
+ flags_parameter(:flags, flags).
176
+ parameter(:type, value_type).
177
+ values_parameter(:source, sources).
178
+ response
179
+ end
180
+ end
181
+
182
+ def remove_column(table_name, column_name)
183
+ if @reverting
184
+ message = "can't revert remove_column(#{table_name}, #{column_name})"
185
+ raise IrreversibleMigrationError, message
186
+ end
187
+
188
+ report(__method__, [table_name, column_name]) do
189
+ @client.request(:column_remove).
190
+ parameter(:table_name, table_name).
191
+ parameter(:name, column_name).
192
+ response
193
+ end
194
+ end
195
+
196
+ def add_timestamp_columns(table_name)
197
+ add_column(table_name, :created_at, :time)
198
+ add_column(table_name, :updated_at, :time)
199
+ end
200
+
201
+ def remove_timestamp_columns(table_name)
202
+ remove_column(table_name, :updated_at)
203
+ remove_column(table_name, :created_at)
204
+ end
205
+
206
+ def copy_column(from_full_column_name,
207
+ to_full_column_name)
208
+ if @reverting
209
+ message = "can't revert copy_column"
210
+ message << "(#{from_full_column_name}, #{to_full_column_name})"
211
+ raise IrreversibleMigrationError, message
212
+ end
213
+
214
+ from_table_name, from_column_name = from_full_column_name.split(".", 2)
215
+ to_table_name, to_column_name = to_full_column_name.split(".", 2)
216
+ report(__method__, [from_full_column_name, to_full_column_name]) do
217
+ @client.request(:column_copy).
218
+ parameter(:from_table, from_table_name).
219
+ parameter(:from_name, from_column_name).
220
+ parameter(:to_table, to_table_name).
221
+ parameter(:to_name, to_column_name).
222
+ response
223
+ end
224
+ end
225
+
226
+ def set_config(key, value)
227
+ if @reverting
228
+ message = "can't revert set_config(#{key.inspect}, #{value.inspect})"
229
+ raise IrreversibleMigrationError, message
230
+ end
231
+
232
+ report(__method__, [key, value]) do
233
+ @client.request(:config_set).
234
+ parameter(:key, key).
235
+ parameter(:value, value).
236
+ response
237
+ end
238
+ end
239
+
240
+ def delete_config(key)
241
+ if @reverting
242
+ message = "can't revert delete_config(#{key.inspect})"
243
+ raise IrreversibleMigrationError, message
244
+ end
245
+
246
+ report(__method__, [key]) do
247
+ @client.request(:config_delete).
248
+ parameter(:key, key).
249
+ response
250
+ end
251
+ end
252
+
253
+ def load(table_name, values, options={})
254
+ if @reverting
255
+ message = "can't revert load(#{table_name.inspect})"
256
+ raise IrreversibleMigrationError, message
257
+ end
258
+
259
+ case values
260
+ when Hash
261
+ json_values = [values].to_json
262
+ when Array
263
+ json_values = values.to_json
264
+ else
265
+ json_values = values
266
+ end
267
+ report(__method__, [table_name]) do
268
+ @client.request(:load).
269
+ parameter(:table, table_name).
270
+ values_parameter(:columns, options[:columns]).
271
+ parameter(:values, json_values).
272
+ response
273
+ end
274
+ end
275
+
276
+ def exist?(name)
277
+ @client.request(:object_exist).
278
+ parameter(:name, name).
279
+ response.
280
+ body
281
+ end
282
+
283
+ def reverting?
284
+ @reverting
285
+ end
286
+
287
+ private
288
+ def puts(*args)
289
+ if @output
290
+ @output.puts(*args)
291
+ else
292
+ super
293
+ end
294
+ end
295
+
296
+ def report(method_name, arguments)
297
+ argument_list = arguments.collect(&:inspect).join(", ")
298
+ puts("-- #{method_name}(#{argument_list})")
299
+ time = Benchmark.measure do
300
+ yield
301
+ end
302
+ puts(" -> %.4fs" % time.real)
303
+ end
304
+
305
+ def revert
306
+ @pending_actions.clear
307
+ @reverting = true
308
+ begin
309
+ yield
310
+ ensure
311
+ @reverting = false
312
+ end
313
+ @pending_actions.reverse_each do |action|
314
+ public_send(*action)
315
+ end
316
+ @pending_actions.clear
317
+ end
318
+
319
+ def normalize_table_type(type)
320
+ case type.to_s
321
+ when "array", /\A(?:TABLE_)?NO_KEY\z/i
322
+ "TABLE_NO_KEY"
323
+ when "hash", "hash_table", /\A(?:TABLE_)?HASH_KEY\z/i
324
+ "TABLE_HASH_KEY"
325
+ when "pat", "patricia_trie", /\A(?:TABLE_)?PAT_KEY\z/i
326
+ "TABLE_PAT_KEY"
327
+ when "dat", "double_array_trie", /\A(?:TABLE_)?DAT_KEY\z/i
328
+ "TABLE_DAT_KEY"
329
+ else
330
+ message = "table type must be one of "
331
+ message << "[:array, :hash_table, :patricia_trie, :double_array_trie]: "
332
+ message << "#{type.inspect}"
333
+ raise ArgumentError, message
334
+ end
335
+ end
336
+
337
+ def normalize_column_type(type)
338
+ case type.to_s
339
+ when "scalar", /\A(?:COLUMN_)?SCALAR\z/i
340
+ "COLUMN_SCALAR"
341
+ when "vector", /\A(?:COLUMN_)?VECTOR\z/i
342
+ "COLUMN_VECTOR"
343
+ when "index", /\A(?:COLUMN_)?INDEX\z/i
344
+ "COLUMN_INDEX"
345
+ else
346
+ message = "table type must be one of "
347
+ message << "[:array, :hash_table, :patricia_trie, :double_array_trie]: "
348
+ message << "#{type.inspect}"
349
+ raise ArgumentError, message
350
+ end
351
+ end
352
+
353
+ def normalize_type(type)
354
+ case type.to_s
355
+ when /\Abool(?:ean)?\z/i
356
+ "Bool"
357
+ when /\Aint(8|16|32|64)\z/i
358
+ "Int#{$1}"
359
+ when /\Auint(8|16|32|64)\z/i
360
+ "UInt#{$1}"
361
+ when /\Afloat\z/i
362
+ "Float"
363
+ when /\Atime\z/i
364
+ "Time"
365
+ when /\Ashort_?text\z/i
366
+ "ShortText"
367
+ when /\Atext\z/i
368
+ "Text"
369
+ when /\Along_?text\z/i
370
+ "LongText"
371
+ when /\Atokyo_?geo_?point\z/i
372
+ "TokyoGeoPoint"
373
+ when /\A(?:wgs84)?_?geo_?point\z/i
374
+ "WGS84GeoPoint"
375
+ else
376
+ type
377
+ end
378
+ end
379
+
380
+ def normalize_tokenizer(tokenizer)
381
+ case tokenizer.to_s
382
+ when /\A(?:token_?)?bigram\z/i
383
+ "TokenBigram"
384
+ when /\A(?:token_?)?delimit\z/i
385
+ "TokenDelimit"
386
+ when /\A(?:token_?)?mecab\z/i
387
+ "TokenMecab"
388
+ else
389
+ tokenizer
390
+ end
391
+ end
392
+
393
+ def normalize_normalizer(normalizer)
394
+ case normalizer.to_s
395
+ when /\A(?:normalizer_?)?auto\z/i
396
+ "NormalizerAuto"
397
+ else
398
+ normalizer
399
+ end
400
+ end
401
+
402
+ class CreateTableMigration
403
+ def initialize(migration, table_name)
404
+ @migration = migration
405
+ @table_name = table_name
406
+ end
407
+
408
+ def boolean(column_name, options={})
409
+ @migration.add_column(@table_name, column_name, :bool, options)
410
+ end
411
+ alias_method :bool, :boolean
412
+
413
+ def integer(column_name, options={})
414
+ options = options.dup
415
+ bit = options.delete(:bit) || 32
416
+ unsigned = options.delete(:unsigned)
417
+ if unsigned
418
+ type = "uint#{bit}"
419
+ else
420
+ type = "int#{bit}"
421
+ end
422
+ @migration.add_column(@table_name, column_name, type, options)
423
+ end
424
+
425
+ def float(column_name, options={})
426
+ @migration.add_column(@table_name, column_name, :float, options)
427
+ end
428
+
429
+ def time(column_name, options={})
430
+ @migration.add_column(@table_name, column_name, :time, options)
431
+ end
432
+
433
+ def short_text(column_name, options={})
434
+ @migration.add_column(@table_name, column_name, :short_text, options)
435
+ end
436
+
437
+ def text(column_name, options={})
438
+ @migration.add_column(@table_name, column_name, :text, options)
439
+ end
440
+
441
+ def long_text(column_name, options={})
442
+ @migration.add_column(@table_name, column_name, :long_text, options)
443
+ end
444
+
445
+ def geo_point(column_name, options={})
446
+ options = options.dup
447
+ datum = options.delete(:datum) || :wgs84
448
+ case datum
449
+ when :wgs84
450
+ type = "WGS84GeoPoint"
451
+ when :tokyo
452
+ type = "TokyoGeoPoint"
453
+ else
454
+ message = "invalid geodetic datum: "
455
+ message << "available: [:wgs84, :tokyo]: #{datum.inspect}"
456
+ raise ArgumentError, message
457
+ end
458
+ @migration.add_column(@table_name, column_name, type, options)
459
+ end
460
+
461
+ def wgs84_geo_point(column_name, options={})
462
+ geo_point(column_name, options.merge(datum: :wgs84))
463
+ end
464
+
465
+ def tokyo_geo_point(column_name, options={})
466
+ geo_point(column_name, options.merge(datum: :tokyo))
467
+ end
468
+
469
+ def reference(column_name, reference_table_name, options={})
470
+ @migration.add_column(@table_name,
471
+ column_name,
472
+ reference_table_name,
473
+ options)
474
+ end
475
+
476
+ def timestamps
477
+ @migration.add_timestamp_columns(@table_name)
478
+ end
479
+
480
+ def index(source_table_name, source_column_names, options={})
481
+ options = options.dup
482
+ source_column_names = Array(source_column_names)
483
+ column_name = options.delete(:name)
484
+ column_name ||= [source_table_name, *source_column_names].join("_")
485
+ @migration.add_column(@table_name,
486
+ column_name,
487
+ source_table_name,
488
+ options.merge(:type => :index,
489
+ :sources => source_column_names))
490
+ end
491
+ end
492
+ end
493
+ end