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.
- checksums.yaml +4 -4
- data/doc/text/news.md +30 -0
- data/groonga-client-model.gemspec +1 -1
- data/lib/groonga_client_model/log_subscriber.rb +1 -3
- data/lib/groonga_client_model/migration.rb +493 -0
- data/lib/groonga_client_model/migrator.rb +240 -0
- data/lib/groonga_client_model/railtie.rb +4 -2
- data/lib/groonga_client_model/railties/groonga.rake +55 -2
- data/lib/groonga_client_model/record.rb +39 -1
- data/lib/groonga_client_model/schema.rb +8 -0
- data/lib/groonga_client_model/schema_loader.rb +4 -9
- data/lib/groonga_client_model/test/groonga_server_runner.rb +19 -3
- data/lib/groonga_client_model/version.rb +1 -1
- data/lib/rails/generators/groonga_client_model/migration/templates/create_table_migration.rb +14 -0
- data/lib/rails/generators/groonga_client_model/migration/templates/delete_config_migration.rb +9 -0
- data/lib/rails/generators/groonga_client_model/migration/templates/migration.rb +12 -0
- data/lib/rails/generators/groonga_client_model/migration/templates/set_config_migration.rb +10 -0
- data/lib/rails/generators/groonga_client_model/migration_generator.rb +125 -0
- data/lib/rails/generators/groonga_client_model/{model/model_generator.rb → model_generator.rb} +18 -4
- data/test/apps/rails4/Gemfile.lock +2 -2
- data/test/apps/rails4/db/groonga/migrate/20170303120517_create_posts.rb +8 -0
- data/test/apps/rails4/db/groonga/migrate/20170303120527_create_terms.rb +7 -0
- data/test/apps/rails4/db/groonga/migrate/20170303120536_create_ages.rb +6 -0
- data/test/apps/rails4/log/development.log +22 -0
- data/test/apps/rails4/log/test.log +1350 -0
- data/test/apps/rails4/test/generators/migration_generator_test.rb +103 -0
- data/test/apps/rails4/test/tmp/db/groonga/migrate/20170307045825_set_config_alias_column.rb +10 -0
- data/test/apps/rails5/Gemfile.lock +8 -8
- data/test/apps/rails5/db/groonga/migrate/20170301061420_create_posts.rb +8 -0
- data/test/apps/rails5/db/groonga/migrate/20170303115054_create_terms.rb +7 -0
- data/test/apps/rails5/db/groonga/migrate/20170303115135_create_ages.rb +6 -0
- data/test/apps/rails5/log/development.log +350 -0
- data/test/apps/rails5/log/test.log +3260 -0
- data/test/apps/rails5/test/generators/migration_generator_test.rb +103 -0
- data/test/apps/rails5/test/tmp/db/groonga/migrate/20170307081511_remove_title_from_posts.rb +5 -0
- data/test/apps/rails5/tmp/cache/assets/sprockets/v3.0/5C/5Cws9GLWcOju_f3tIpY01qSaj7zkLAU0a2bQmpf7sS8.cache +1 -0
- data/test/apps/rails5/tmp/cache/assets/sprockets/v3.0/XH/XH9pWZvGgK476BpPGID5z8hjzRmGJjtzV0cHBLXhIKc.cache +1 -0
- data/test/unit/fixtures/migrate/20170301061420_create_posts.rb +8 -0
- data/test/unit/fixtures/migrate/20170303115054_create_terms.rb +7 -0
- data/test/unit/fixtures/migrate/20170303115135_create_ages.rb +6 -0
- data/test/unit/migration/test_config.rb +62 -0
- data/test/unit/migration/test_copy.rb +117 -0
- data/test/unit/migration/test_create_table.rb +528 -0
- data/test/unit/migration/test_exist.rb +47 -0
- data/test/unit/migration/test_load.rb +83 -0
- data/test/unit/record/test_active_model.rb +31 -0
- data/test/unit/record/test_readers.rb +45 -0
- data/test/unit/record/test_timestamps.rb +76 -0
- data/test/unit/record/test_validators.rb +295 -0
- data/test/unit/run-test.rb +1 -2
- data/test/unit/test_helper.rb +109 -0
- data/test/unit/test_load_value_generator.rb +8 -7
- data/test/unit/test_migrator.rb +156 -0
- metadata +64 -11
- data/test/apps/rails4/db/schema.grn +0 -9
- data/test/apps/rails5/db/schema.grn +0 -11
- 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
|
-
|
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
|
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(*
|
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(
|
22
|
-
@
|
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
|
-
|
35
|
-
|
36
|
-
parser << line
|
37
|
-
end
|
31
|
+
@schema.each_line do |line|
|
32
|
+
parser << line
|
38
33
|
end
|
39
34
|
parser.finish
|
40
35
|
end
|