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
@@ -0,0 +1,240 @@
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 "groonga_client_model/migration"
18
+
19
+ module GroongaClientModel
20
+ class Migrator
21
+ class << self
22
+ def next_migration_number(number)
23
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
24
+ end
25
+
26
+ def default_search_path
27
+ "db/groonga/migrate"
28
+ end
29
+ end
30
+
31
+ attr_accessor :output
32
+ attr_reader :current_version
33
+
34
+ def initialize(search_paths)
35
+ @output = nil
36
+ @search_paths = Array(search_paths)
37
+ ensure_versions
38
+ ensure_loaded_versions
39
+ @current_version = @loaded_versions.last
40
+ @target_version = nil
41
+ end
42
+
43
+ def target_version=(version)
44
+ @target_version = version
45
+ end
46
+
47
+ def step=(step)
48
+ if @current_version.nil?
49
+ index = step - 1
50
+ else
51
+ index = @versions.index(@current_version)
52
+ index += step
53
+ end
54
+ if index < 0
55
+ version = 0
56
+ else
57
+ version = @versions[index]
58
+ end
59
+ self.target_version = version
60
+ end
61
+
62
+ def migrate
63
+ is_forward = forward?
64
+ each do |definition|
65
+ Client.open do |client|
66
+ migration = definition.create_migration(client)
67
+ migration.output = @output
68
+ report(definition) do
69
+ if is_forward
70
+ migration.up
71
+ add_version(client, definition.version)
72
+ @current_version = definition.version
73
+ else
74
+ migration.down
75
+ delete_version(client, definition.version)
76
+ previous_version_index = @versions.index(definition.version) - 1
77
+ if previous_version_index < 0
78
+ @current_version = nil
79
+ else
80
+ @current_version = @versions[previous_version_index]
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def each
89
+ return to_enum(:each) unless block_given?
90
+
91
+ current_version = @current_version || 0
92
+ if forward?
93
+ sorted_definitions.each do |definition|
94
+ next if definition.version <= current_version
95
+ next if @target_version and definition.version > @target_version
96
+ yield(definition)
97
+ end
98
+ else
99
+ sorted_definitions.reverse_each do |definition|
100
+ next if definition.version > current_version
101
+ next if @target_version and definition.version <= @target_version
102
+ yield(definition)
103
+ end
104
+ end
105
+ end
106
+
107
+ private
108
+ def puts(*args)
109
+ if @output
110
+ @output.puts(*args)
111
+ else
112
+ super
113
+ end
114
+ end
115
+
116
+ def version_table_name
117
+ "schema_versions"
118
+ end
119
+
120
+ def collect_definitions
121
+ paths = []
122
+ @search_paths.each do |search_path|
123
+ paths |= Dir.glob("#{search_path}/**/[0-9]*_*.rb").collect do |path|
124
+ File.expand_path(path)
125
+ end
126
+ end
127
+ definitions = []
128
+ paths.each do |path|
129
+ definition = Definition.new(path)
130
+ definitions << definition if definition.valid?
131
+ end
132
+ definitions
133
+ end
134
+
135
+ def sorted_definitions
136
+ @sorted_definitions ||= collect_definitions.sort_by(&:version)
137
+ end
138
+
139
+ def ensure_versions
140
+ @versions = sorted_definitions.collect(&:version)
141
+ end
142
+
143
+ def ensure_loaded_versions
144
+ Client.open do |client|
145
+ table_name = version_table_name
146
+ exist = client.object_exist(name: table_name).body
147
+ if exist
148
+ @loaded_versions = client.request(:select).
149
+ parameter(:table, table_name).
150
+ sort_keys([:_key]).
151
+ limit(-1).
152
+ output_columns(["_key"]).
153
+ response.
154
+ records.
155
+ collect(&:_key)
156
+ else
157
+ client.request(:table_create).
158
+ parameter(:name, table_name).
159
+ flags_parameter(:flags, ["TABLE_PAT_KEY"]).
160
+ parameter(:key_type, "UInt64").
161
+ response
162
+ @loaded_versions = []
163
+ end
164
+ end
165
+ end
166
+
167
+ def forward?
168
+ @target_version.nil? or
169
+ @current_version.nil? or
170
+ (@target_version > @current_version)
171
+ end
172
+
173
+ def add_version(client, version)
174
+ client.request(:load).
175
+ parameter(:table, version_table_name).
176
+ parameter(:values, [{"_key" => version}].to_json).
177
+ response
178
+ end
179
+
180
+ def delete_version(client, version)
181
+ client.request(:delete).
182
+ parameter(:table, version_table_name).
183
+ parameter(:key, version).
184
+ response
185
+ end
186
+
187
+ def report(definition)
188
+ version = definition.version
189
+ name = definition.name
190
+ if forward?
191
+ action = "forward"
192
+ else
193
+ action = "rollback"
194
+ end
195
+ mark("#{version} #{name}: #{action}")
196
+ time = Benchmark.measure do
197
+ yield
198
+ end
199
+ mark("%s %s: %.4fs" % [version, name, time.real])
200
+ puts
201
+ end
202
+
203
+ def mark(text)
204
+ pre = "=="
205
+ max_width = 79
206
+ post_length = [0, max_width - pre.length - 1 - text.length - 1].max
207
+ post = "=" * post_length
208
+ puts("#{pre} #{text} #{post}")
209
+ end
210
+
211
+ class Definition
212
+ attr_reader :version
213
+ attr_reader :name
214
+ def initialize(path)
215
+ @path = path
216
+ parse_path
217
+ end
218
+
219
+ def valid?
220
+ @version and @name and File.exist?(@path)
221
+ end
222
+
223
+ def create_migration(client)
224
+ require(@path)
225
+ @name.camelize.constantize.new(client)
226
+ end
227
+
228
+ private
229
+ def parse_path
230
+ if /\A([0-9]+)_([_a-z0-9]+)\.rb\z/ =~ File.basename(@path)
231
+ @version = $1.to_i
232
+ @name = $2
233
+ else
234
+ @version = nil
235
+ @name = nil
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
@@ -21,8 +21,10 @@ module GroongaClientModel
21
21
  class Railtie < Rails::Railtie
22
22
  config.groonga_client_model = ActiveSupport::OrderedOptions.new
23
23
 
24
- if config.app_generators.orm.empty?
25
- config.app_generators.orm(:groonga_client_model)
24
+ unless config.app_generators.rails[:orm]
25
+ config.app_generators.orm(:groonga_client_model,
26
+ migration: true,
27
+ timestamps: true)
26
28
  end
27
29
 
28
30
  config.action_dispatch.rescue_responses.merge!(
@@ -1,6 +1,6 @@
1
1
  # -*- ruby -*-
2
2
  #
3
- # Copyright (C) 2016 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2016-2017 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -16,6 +16,8 @@
16
16
  # License along with this library; if not, write to the Free Software
17
17
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
 
19
+ require "groonga_client_model/migrator"
20
+
19
21
  namespace :groonga do
20
22
  namespace :config do
21
23
  desc "Load config/groonga.rb"
@@ -26,10 +28,61 @@ namespace :groonga do
26
28
  end
27
29
 
28
30
  namespace :schema do
29
- desc "Loads db/schema.grn info Groonga"
31
+ desc "Loads db/schema.grn into the Groonga database"
30
32
  task load: ["config:load"] do
31
33
  schema_loader = GroongaClientModel::SchemaLoader.new(Rails.root)
32
34
  schema_loader.load
33
35
  end
34
36
  end
37
+
38
+ namespace :migrate do
39
+ task setup: ["config:load"] do
40
+ Rails.application.paths["db/groonga/migrate"] ||=
41
+ GroongaClientModel::Migrator.default_search_path
42
+ end
43
+
44
+ desc "Rollbacks the Groonga database one version and re-migrates"
45
+ task redo: ["setup"] do
46
+ migration_paths = Rails.application.paths["db/groonga/migrate"].to_a
47
+ migrator = GroongaClientModel::Migrator.new(migration_paths)
48
+ current_version = migrator.current_version
49
+ if ENV["VERSION"]
50
+ migrator.target_version = Integer(ENV["VERSION"])
51
+ migrator.migrate
52
+ migrator.target_version = current_version
53
+ migrator.migrate
54
+ elsif current_version
55
+ if ENV["STEP"]
56
+ step = Integer(ENV["STEP"])
57
+ else
58
+ step = 1
59
+ end
60
+ migrator.step = -step
61
+ migrator.migrate
62
+ migrator.target_version = current_version
63
+ migrator.migrate
64
+ end
65
+ end
66
+
67
+ desc "Rolls the Groonga database back to the previous version"
68
+ task rollback: ["setup"] do
69
+ migration_paths = Rails.application.paths["db/groonga/migrate"].to_a
70
+ migrator = GroongaClientModel::Migrator.new(migration_paths)
71
+ if ENV["STEP"]
72
+ step = Integer(ENV["STEP"])
73
+ else
74
+ step = 1
75
+ end
76
+ migrator.step = -step
77
+ migrator.migrate
78
+ end
79
+ end
80
+
81
+ desc "Migrates the Groonga database"
82
+ task migrate: ["migrate:setup"] do
83
+ migration_paths = Rails.application.paths["db/groonga/migrate"].to_a
84
+ migrator = GroongaClientModel::Migrator.new(migration_paths)
85
+ migrator.target_version = Integer(ENV["VERSION"]) if ENV["VERSION"]
86
+ migrator.migrate
87
+ end
35
88
  end
@@ -52,8 +52,21 @@ module GroongaClientModel
52
52
  def define_attributes
53
53
  return if defined?(@defined)
54
54
  @defined = true
55
+
56
+ boolean_column_names = []
57
+ non_boolean_column_names = []
58
+ columns.each do |name, column|
59
+ if (column["value_type"] || {})["name"] == "Bool"
60
+ boolean_column_names << name
61
+ else
62
+ non_boolean_column_names << name
63
+ end
64
+ end
65
+
55
66
  attribute_method_suffix("=")
56
- define_attribute_methods(*columns.names)
67
+ define_attribute_methods(*non_boolean_column_names)
68
+ attribute_method_suffix("?")
69
+ define_attribute_methods(*boolean_column_names)
57
70
  end
58
71
 
59
72
  def count
@@ -65,6 +78,9 @@ module GroongaClientModel
65
78
  end
66
79
 
67
80
  def find(id)
81
+ if id.respond_to?(:id)
82
+ id = id.id
83
+ end
68
84
  record = select.filter("_id == %{id}", id: id).limit(1).first
69
85
  if record.nil?
70
86
  raise RecordNotFound.new("Record not found: _id: <#{id}>")
@@ -132,6 +148,12 @@ module GroongaClientModel
132
148
  @attributes[name] = value
133
149
  end
134
150
  end
151
+
152
+ def define_method_attribute?(name)
153
+ define_method("#{name}?") do
154
+ @attributes[name]
155
+ end
156
+ end
135
157
  end
136
158
 
137
159
  define_model_callbacks :save, :create, :update, :destroy
@@ -264,6 +286,8 @@ module GroongaClientModel
264
286
  def upsert_raw(validate: true)
265
287
  return false unless upsert_sub_records(validate: validate)
266
288
 
289
+ update_timestamps
290
+
267
291
  Client.open do |client|
268
292
  table = self.class.schema.tables[self.class.table_name]
269
293
  load_value_generator = LoadValueGenerator.new(self)
@@ -369,5 +393,19 @@ module GroongaClientModel
369
393
  sub_record_class.new(value)
370
394
  end
371
395
  end
396
+
397
+ def update_timestamps
398
+ now = Time.now
399
+
400
+ if new_record?
401
+ [:created_at, :created_on].each do |attribute|
402
+ __send__("#{attribute}=", now) if respond_to?("#{attribute}=")
403
+ end
404
+ end
405
+
406
+ [:updated_at, :updated_on].each do |attribute|
407
+ __send__("#{attribute}=", now) if respond_to?("#{attribute}=")
408
+ end
409
+ end
372
410
  end
373
411
  end
@@ -70,6 +70,14 @@ module GroongaClientModel
70
70
  Columns.new(@raw_schema, @raw_table.columns.merge(raw_columns))
71
71
  end
72
72
 
73
+ def tokenizer
74
+ @raw_table.tokenizer
75
+ end
76
+
77
+ def normalizer
78
+ @raw_table.normalizer
79
+ end
80
+
73
81
  private
74
82
  def create_pseudo_column(name, value_type)
75
83
  raw_column = {
@@ -18,23 +18,18 @@ require "groonga/command/parser"
18
18
 
19
19
  module GroongaClientModel
20
20
  class SchemaLoader
21
- def initialize(base_dir)
22
- @base_dir = base_dir
21
+ def initialize(schema)
22
+ @schema = schema
23
23
  end
24
24
 
25
25
  def load
26
- schema_path = @base_dir + "db" + "schema.grn"
27
- return unless schema_path.exist?
28
-
29
26
  Client.open do |client|
30
27
  parser = Groonga::Command::Parser.new
31
28
  parser.on_command do |command|
32
29
  client.execute(command)
33
30
  end
34
- schema_path.open do |schema_file|
35
- schema_file.each_line do |line|
36
- parser << line
37
- end
31
+ @schema.each_line do |line|
32
+ parser << line
38
33
  end
39
34
  parser.finish
40
35
  end