groonga-client-model 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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