aam 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.org +119 -0
- data/Rakefile +10 -0
- data/aam.gemspec +30 -0
- data/examples/0100_example.rb +112 -0
- data/experiment/000_example.rb +121 -0
- data/lib/aam.rb +15 -0
- data/lib/aam/annotation.rb +229 -0
- data/lib/aam/railtie.rb +7 -0
- data/lib/aam/schema_info_generator.rb +313 -0
- data/lib/aam/tasks/aam.rake +19 -0
- data/lib/aam/version.rb +3 -0
- data/test/test_aam.rb +147 -0
- data/test/test_helper.rb +3 -0
- metadata +175 -0
data/lib/aam.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "active_support/core_ext/string/filters"
|
3
|
+
require "org_tp"
|
4
|
+
|
5
|
+
module Aam
|
6
|
+
SCHEMA_HEADER = "# == Schema Information =="
|
7
|
+
|
8
|
+
mattr_accessor :logger
|
9
|
+
self.logger = ActiveSupport::Logger.new(STDOUT)
|
10
|
+
end
|
11
|
+
|
12
|
+
require "aam/version"
|
13
|
+
require "aam/schema_info_generator"
|
14
|
+
require "aam/annotation"
|
15
|
+
require "aam/railtie" if defined? Rails::Railtie
|
@@ -0,0 +1,229 @@
|
|
1
|
+
module Aam
|
2
|
+
class Annotation
|
3
|
+
MAGIC_COMMENT_LINE = "# -*- coding: utf-8 -*-\n"
|
4
|
+
|
5
|
+
attr_accessor :counts, :options
|
6
|
+
|
7
|
+
def self.run(options = {})
|
8
|
+
new(options).run
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = {
|
13
|
+
:root_dir => Rails.root,
|
14
|
+
:dry_run => false,
|
15
|
+
:skip_columns => [], # %w(id created_at updated_at),
|
16
|
+
:models => ENV["MODEL"].presence || ENV["MODELS"].presence,
|
17
|
+
}.merge(options)
|
18
|
+
@counts = Hash.new(0)
|
19
|
+
STDOUT.sync = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
schema_info_text_write
|
24
|
+
puts
|
25
|
+
model_file_write_all
|
26
|
+
end
|
27
|
+
|
28
|
+
def model_file_write_all
|
29
|
+
target_ar_klasses_from_model_filenames.each do |klass|
|
30
|
+
begin
|
31
|
+
model = Model.new(self, klass)
|
32
|
+
model.write_to_relation_files
|
33
|
+
rescue ActiveRecord::ActiveRecordError => error
|
34
|
+
if @options[:debug]
|
35
|
+
puts "--------------------------------------------------------------------------------"
|
36
|
+
p error
|
37
|
+
puts "--------------------------------------------------------------------------------"
|
38
|
+
end
|
39
|
+
@counts[:error] += 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
puts "#{@counts[:success]} success, #{@counts[:skip]} skip, #{@counts[:error]} errors"
|
43
|
+
end
|
44
|
+
|
45
|
+
def schema_info_text_write
|
46
|
+
@all = []
|
47
|
+
target_ar_klasses_from_model_require_and_ar_subclasses.each do |klass|
|
48
|
+
begin
|
49
|
+
model = Model.new(self, klass)
|
50
|
+
@all << model.schema_info
|
51
|
+
rescue ActiveRecord::ActiveRecordError => error
|
52
|
+
end
|
53
|
+
end
|
54
|
+
file = root_dir.join("db", "schema_info.txt")
|
55
|
+
magic_comment = "-*- truncate-lines: t -*-"
|
56
|
+
file.write("#{magic_comment}\n\n#{@all.join}")
|
57
|
+
puts "output: #{file} (#{@all.size} counts)"
|
58
|
+
end
|
59
|
+
|
60
|
+
def root_dir
|
61
|
+
@root_dir ||= Pathname(@options[:root_dir].to_s).expand_path
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
class Model
|
67
|
+
def initialize(base, klass)
|
68
|
+
@base = base
|
69
|
+
@klass = klass
|
70
|
+
end
|
71
|
+
|
72
|
+
def schema_info
|
73
|
+
@schema_info ||= SchemaInfoGenerator.new(@klass, @base.options).generate + "\n"
|
74
|
+
end
|
75
|
+
|
76
|
+
def write_to_relation_files
|
77
|
+
puts "--------------------------------------------------------------------------------"
|
78
|
+
puts "--> #{@klass}"
|
79
|
+
target_files = search_paths.collect {|search_path|
|
80
|
+
v = Pathname.glob((@base.root_dir + search_path).expand_path)
|
81
|
+
v.reject{|e|e.to_s.include?("node_modules")}
|
82
|
+
}.flatten.uniq
|
83
|
+
target_files.each {|e| annotate_write(e) }
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# TODO: アプリの構成に依存しすぎ?
|
89
|
+
def search_paths
|
90
|
+
paths = []
|
91
|
+
paths << "app/models/**/#{@klass.name.underscore}.rb"
|
92
|
+
# paths << "app/models/**/#{@klass.name.underscore}_{search,observer,callback,sweeper}.rb"
|
93
|
+
paths << "test/unit/**/#{@klass.name.underscore}_test.rb"
|
94
|
+
paths << "test/fixtures/**/#{@klass.name.underscore.pluralize}.yml"
|
95
|
+
paths << "test/unit/helpers/**/#{@klass.name.underscore}_helper_test.rb"
|
96
|
+
paths << "spec/models/**/#{@klass.name.underscore}_spec.rb"
|
97
|
+
paths << "{test,spec}/**/#{@klass.name.underscore}_factory.rb"
|
98
|
+
[:pluralize, :singularize].each{|method|
|
99
|
+
prefix = @klass.name.underscore.send(method)
|
100
|
+
[
|
101
|
+
"app/controllers/**/#{prefix}_controller.rb",
|
102
|
+
"app/helpers/**/#{prefix}_helper.rb",
|
103
|
+
"test/functional/**/#{prefix}_controller_test.rb",
|
104
|
+
"test/factories/**/#{prefix}_factory.rb",
|
105
|
+
"test/factories/**/#{prefix}.rb",
|
106
|
+
"db/seeds/**/{[0-9]*_,}#{prefix}_setup.rb",
|
107
|
+
"db/seeds/**/{[0-9]*_,}#{prefix}_seed.rb",
|
108
|
+
"db/seeds/**/{[0-9]*_,}#{prefix}.rb",
|
109
|
+
"db/migrate/*_{create,to,from}_#{prefix}.rb",
|
110
|
+
"spec/**/#{prefix}_{controller,helper}_spec.rb",
|
111
|
+
].each{|path|
|
112
|
+
paths << path
|
113
|
+
}
|
114
|
+
}
|
115
|
+
paths
|
116
|
+
end
|
117
|
+
|
118
|
+
def annotate_write(file_name)
|
119
|
+
body = file_name.read
|
120
|
+
regexp = /^#{SCHEMA_HEADER}\n(#.*\n)*\n+/
|
121
|
+
if body.match(regexp)
|
122
|
+
body = body.sub(regexp, schema_info)
|
123
|
+
elsif body.include?(MAGIC_COMMENT_LINE)
|
124
|
+
body = body.sub(/#{Regexp.escape(MAGIC_COMMENT_LINE)}\s*/) {MAGIC_COMMENT_LINE + schema_info}
|
125
|
+
else
|
126
|
+
body = body.sub(/^\s*/, schema_info)
|
127
|
+
end
|
128
|
+
body = insert_magick_comment(body)
|
129
|
+
unless @base.options[:dry_run]
|
130
|
+
file_name.write(body)
|
131
|
+
end
|
132
|
+
puts "write: #{file_name}"
|
133
|
+
@base.counts[:success] += 1
|
134
|
+
end
|
135
|
+
|
136
|
+
def insert_magick_comment(body, force = false)
|
137
|
+
if force
|
138
|
+
body = body.sub(/#{Regexp.escape(MAGIC_COMMENT_LINE)}\s*/, "")
|
139
|
+
end
|
140
|
+
unless body.include?(MAGIC_COMMENT_LINE)
|
141
|
+
body = body.sub(/^\s*/, MAGIC_COMMENT_LINE)
|
142
|
+
end
|
143
|
+
body
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# テーブルを持っているクラスたち
|
149
|
+
#
|
150
|
+
def target_ar_klasses
|
151
|
+
target_ar_klasses_from_model_require_and_ar_subclasses
|
152
|
+
# ActiveRecord::Base.subclasses
|
153
|
+
end
|
154
|
+
|
155
|
+
# すべての app/models/**/*.rb を require したあと ActiveRecord::Base.subclasses を参照
|
156
|
+
def target_ar_klasses_from_model_require_and_ar_subclasses
|
157
|
+
target_model_files.each do |file|
|
158
|
+
begin
|
159
|
+
silence_warnings do
|
160
|
+
require file
|
161
|
+
end
|
162
|
+
puts "require: #{file}"
|
163
|
+
rescue Exception
|
164
|
+
end
|
165
|
+
end
|
166
|
+
if defined?(ApplicationRecord)
|
167
|
+
ApplicationRecord.subclasses
|
168
|
+
else
|
169
|
+
ActiveRecord::Base.subclasses
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# app/models/* のファイル名を constantize してみることでクラスを収集する
|
174
|
+
def target_ar_klasses_from_model_filenames
|
175
|
+
models = []
|
176
|
+
target_model_files.each do |file|
|
177
|
+
file = file.expand_path
|
178
|
+
klass = nil
|
179
|
+
|
180
|
+
md = file.to_s.match(/\A.*\/app\/models\/(.*)\.rb\z/)
|
181
|
+
underscore_class_name = md.captures.first
|
182
|
+
class_name = underscore_class_name.camelize # classify だと boss が bos になってしまう
|
183
|
+
begin
|
184
|
+
klass = class_name.constantize
|
185
|
+
rescue LoadError => error # LoadError は rescue nil では捕捉できないため
|
186
|
+
puts "#{class_name} に対応するファイルは見つかりませんでした : #{error}"
|
187
|
+
rescue
|
188
|
+
end
|
189
|
+
|
190
|
+
# klass.class == Class を入れないと [] < ActiveRecord::Base のときにエラーになる
|
191
|
+
if klass && klass.class == Class && klass < ActiveRecord::Base && !klass.abstract_class?
|
192
|
+
# puts "#{file} は ActiveRecord::Base のサブクラスなので対象とします。"
|
193
|
+
puts "model: #{file}"
|
194
|
+
models << klass
|
195
|
+
else
|
196
|
+
# puts "#{file} (クラス名:#{class_name}) は ActiveRecord::Base のサブクラスではありませんでした。"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
models
|
200
|
+
end
|
201
|
+
|
202
|
+
#
|
203
|
+
# 対象のモデルファイル
|
204
|
+
#
|
205
|
+
def target_model_files
|
206
|
+
files = []
|
207
|
+
files += Pathname.glob("#{root_dir}/app/models/**/*.rb")
|
208
|
+
files += Pathname.glob("#{root_dir}/vendor/plugins/*/app/models/**/*.rb")
|
209
|
+
if @options[:models]
|
210
|
+
@options[:models].split(",").collect { |m|
|
211
|
+
files.find_all { |e|
|
212
|
+
e.basename(".*").to_s.match(/#{m.camelize}|#{m.underscore}/i)
|
213
|
+
}
|
214
|
+
}.flatten.uniq
|
215
|
+
else
|
216
|
+
files
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
if $0 == __FILE__
|
223
|
+
require "active_record"
|
224
|
+
require "rails"
|
225
|
+
require "org_tp"
|
226
|
+
obj = Aam::Annotation.new(root_dir: "~/src/shogi_web")
|
227
|
+
tp obj.send(:target_model_files)
|
228
|
+
tp obj.send(:target_ar_klasses_from_model_filenames)
|
229
|
+
end
|
data/lib/aam/railtie.rb
ADDED
@@ -0,0 +1,313 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "active_support/core_ext/string/filters"
|
3
|
+
require "org_tp"
|
4
|
+
|
5
|
+
module Aam
|
6
|
+
class SchemaInfoGenerator
|
7
|
+
def initialize(klass, options = {})
|
8
|
+
@klass = klass
|
9
|
+
@options = {
|
10
|
+
:skip_columns => [],
|
11
|
+
:debug => false,
|
12
|
+
}.merge(options)
|
13
|
+
@memos = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate
|
17
|
+
columns = @klass.columns.reject { |e|
|
18
|
+
@options[:skip_columns].include?(e.name)
|
19
|
+
}
|
20
|
+
rows = columns.collect {|e|
|
21
|
+
{
|
22
|
+
"カラム名" => e.name,
|
23
|
+
"意味" => column_to_human_name(e.name),
|
24
|
+
"タイプ" => column_type_inspect_of(e),
|
25
|
+
"属性" => column_attribute_inspect_of(e),
|
26
|
+
"参照" => reflections_inspect_of(e),
|
27
|
+
"INDEX" => index_info(e),
|
28
|
+
}
|
29
|
+
}
|
30
|
+
out = []
|
31
|
+
out << "#{SCHEMA_HEADER}\n#\n"
|
32
|
+
out << "# #{@klass.model_name.human}テーブル (#{@klass.table_name} as #{@klass.name})\n"
|
33
|
+
out << "#\n"
|
34
|
+
out << rows.to_t.lines.collect { |e| "# #{e}" }.join
|
35
|
+
if @memos.present?
|
36
|
+
out << "#\n"
|
37
|
+
out << "#- 備考 -------------------------------------------------------------------------\n"
|
38
|
+
out << @memos.collect{|row|"# ・#{row}\n"}.join
|
39
|
+
out << "#--------------------------------------------------------------------------------\n"
|
40
|
+
end
|
41
|
+
out.join
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def column_type_inspect_of(column)
|
47
|
+
size = nil
|
48
|
+
if column.type.to_s == "decimal"
|
49
|
+
size = "(#{column.precision}, #{column.scale})"
|
50
|
+
else
|
51
|
+
if column.limit
|
52
|
+
size = "(#{column.limit})"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# シリアライズされているかチェック
|
57
|
+
serialized_klass = nil
|
58
|
+
if @klass.respond_to?(:serialized_attributes) # Rails5 から無くなったため存在チェック
|
59
|
+
if serialized_klass = @klass.serialized_attributes[column.name]
|
60
|
+
if serialized_klass.kind_of? ActiveRecord::Coders::YAMLColumn
|
61
|
+
serialized_klass = "=> #{serialized_klass.object_class}"
|
62
|
+
else
|
63
|
+
serialized_klass = "=> #{serialized_klass}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
"#{column.type}#{size} #{serialized_klass}".squish
|
69
|
+
end
|
70
|
+
|
71
|
+
def column_attribute_inspect_of(column)
|
72
|
+
attrs = []
|
73
|
+
unless column.default.nil?
|
74
|
+
default = column.default
|
75
|
+
if default.kind_of? BigDecimal
|
76
|
+
default = default.to_f
|
77
|
+
if default.zero?
|
78
|
+
default = 0
|
79
|
+
end
|
80
|
+
end
|
81
|
+
attrs << "DEFAULT(#{default})"
|
82
|
+
end
|
83
|
+
unless column.null
|
84
|
+
attrs << "NOT NULL"
|
85
|
+
end
|
86
|
+
if column.name == @klass.primary_key
|
87
|
+
attrs << "PK"
|
88
|
+
end
|
89
|
+
attrs * " "
|
90
|
+
end
|
91
|
+
|
92
|
+
def reflections_inspect_of(column)
|
93
|
+
[reflections_inspect_ary_of(column)].flatten.compact.sort.join(" と ")
|
94
|
+
end
|
95
|
+
|
96
|
+
def reflections_inspect_ary_of(column)
|
97
|
+
if column.name == @klass.inheritance_column # カラムが "type" のとき
|
98
|
+
return "モデル名(STI)"
|
99
|
+
end
|
100
|
+
|
101
|
+
index_check(column)
|
102
|
+
|
103
|
+
my_refrections = @klass.reflections.find_all do |key, reflection|
|
104
|
+
if !reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) && reflection.respond_to?(:foreign_key)
|
105
|
+
reflection.foreign_key.to_s == column.name
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
if my_refrections.empty?
|
110
|
+
# "xxx_id" は belongs_to されていることを確認
|
111
|
+
if md = column.name.match(/(\w+)_id\z/)
|
112
|
+
name = md.captures.first
|
113
|
+
if @klass.column_names.include?("#{name}_type")
|
114
|
+
syntax = "belongs_to :#{name}, :polymorphic => true"
|
115
|
+
else
|
116
|
+
syntax = "belongs_to :#{name}"
|
117
|
+
end
|
118
|
+
memo_puts "【警告】#{@klass} モデルに #{syntax} を追加してください"
|
119
|
+
else
|
120
|
+
# "xxx_type" は polymorphic 指定されていることを確認
|
121
|
+
key, reflection = @klass.reflections.find do |key, reflection|
|
122
|
+
_options = reflection.options
|
123
|
+
if true
|
124
|
+
# >= 3.1.3
|
125
|
+
_options[:polymorphic] && column.name == "#{key}_type"
|
126
|
+
else
|
127
|
+
# < 3.1.3
|
128
|
+
_options[:polymorphic] && _options[:foreign_type] == column.name
|
129
|
+
end
|
130
|
+
end
|
131
|
+
if reflection
|
132
|
+
"モデル名(polymorphic)"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
else
|
136
|
+
# 一つのカラムを複数の方法で利用している場合に対応するため回している。
|
137
|
+
my_refrections.collect do |key, reflection|
|
138
|
+
begin
|
139
|
+
reflection_inspect_of(column, reflection)
|
140
|
+
rescue NameError => error
|
141
|
+
if @options[:debug]
|
142
|
+
puts "--------------------------------------------------------------------------------"
|
143
|
+
puts "【警告】以下のクラスがないため NameError になっちゃってます"
|
144
|
+
p error
|
145
|
+
puts "--------------------------------------------------------------------------------"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def reflection_inspect_of(column, reflection)
|
153
|
+
return unless reflection.macro == :belongs_to
|
154
|
+
desc = nil
|
155
|
+
if reflection.options[:polymorphic]
|
156
|
+
if true
|
157
|
+
# >= 3.1.3
|
158
|
+
target = "(#{reflection.name}_type)##{reflection.active_record.primary_key}"
|
159
|
+
else
|
160
|
+
# < 3.1.3
|
161
|
+
target = "(#{reflection.options[:foreign_type]})##{reflection.active_record.primary_key}"
|
162
|
+
end
|
163
|
+
else
|
164
|
+
target = "#{reflection.class_name}##{reflection.active_record.primary_key}"
|
165
|
+
desc = belongs_to_model_has_many_syntax(column, reflection)
|
166
|
+
end
|
167
|
+
assoc_name = ""
|
168
|
+
unless "#{reflection.name}_id" == column.name
|
169
|
+
assoc_name = ":#{reflection.name}"
|
170
|
+
end
|
171
|
+
"#{assoc_name} => #{target} #{desc}".squish
|
172
|
+
end
|
173
|
+
|
174
|
+
# belongs_to :user している場合 User モデルから has_many :articles されていることを確認。
|
175
|
+
#
|
176
|
+
# 1. assoc_reflection.foreign_key.to_s == column.name という比較では foreign_key 指定されると不一致になるので注意すること。
|
177
|
+
# と書いたけど不一致になってもよかった。これでリレーション正しく貼られてないと判断してよい。
|
178
|
+
# 理由は belongs_to に foreign_key が指定されたら has_many 側も has_many :foos, :foreign_key => "bar_id" とならないといけないため。
|
179
|
+
#
|
180
|
+
def belongs_to_model_has_many_syntax(column, reflection)
|
181
|
+
assoc_key, assoc_reflection = reflection.class_name.constantize.reflections.find do |assoc_key, assoc_reflection|
|
182
|
+
if false
|
183
|
+
r = reflection.class_name.constantize == assoc_reflection.active_record && [:has_many, :has_one].include?(assoc_reflection.macro)
|
184
|
+
else
|
185
|
+
r = assoc_reflection.respond_to?(:foreign_key) && assoc_reflection.foreign_key.to_s == column.name
|
186
|
+
end
|
187
|
+
if r
|
188
|
+
syntax = ["#{assoc_reflection.macro} :#{assoc_reflection.name}"]
|
189
|
+
if assoc_reflection.options[:foreign_key]
|
190
|
+
syntax << ":foreign_key => :#{assoc_reflection.options[:foreign_key]}"
|
191
|
+
end
|
192
|
+
memo_puts "#{@klass.name} モデルは #{assoc_reflection.active_record} モデルから #{syntax.join(', ')} されています。"
|
193
|
+
r
|
194
|
+
end
|
195
|
+
end
|
196
|
+
unless assoc_reflection
|
197
|
+
syntax = ["has_many :#{@klass.name.underscore.pluralize}"]
|
198
|
+
if false
|
199
|
+
# has_many :sub_articles の場合デフォルトで SubArticle を見るため不要
|
200
|
+
syntax << ":class_name => \"#{@klass.name}\""
|
201
|
+
end
|
202
|
+
if reflection.options[:foreign_key]
|
203
|
+
syntax << ":foreign_key => :#{reflection.options[:foreign_key]}"
|
204
|
+
end
|
205
|
+
memo_puts "【警告:リレーション欠如】#{reflection.class_name}モデルで #{syntax.join(', ')} されていません"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# カラム翻訳
|
210
|
+
#
|
211
|
+
# ja.rb:
|
212
|
+
# :item => "アイテム"
|
213
|
+
#
|
214
|
+
# 実行結果:
|
215
|
+
# column_to_human_name("item") #=> "アイテム"
|
216
|
+
# column_to_human_name("item_id") #=> "アイテムID"
|
217
|
+
#
|
218
|
+
def column_to_human_name(name)
|
219
|
+
resp = nil
|
220
|
+
suffixes = {
|
221
|
+
:id => "ID",
|
222
|
+
:type => "タイプ",
|
223
|
+
}
|
224
|
+
suffixes.each do |key, value|
|
225
|
+
if md = name.match(/(?<name_without_suffix>\w+)_#{key}$/)
|
226
|
+
# サフィックス付きのまま明示的に翻訳されている場合はそれを使う
|
227
|
+
resp = @klass.human_attribute_name(name, :default => "").presence
|
228
|
+
# サフィックスなしが明示的に翻訳されていたらそれを使う
|
229
|
+
unless resp
|
230
|
+
if v = @klass.human_attribute_name(md[:name_without_suffix], :default => "").presence
|
231
|
+
resp = "#{v}#{value}"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
if resp
|
236
|
+
break
|
237
|
+
end
|
238
|
+
end
|
239
|
+
# 翻訳が効いてないけどid付きのまま仕方なく変換する
|
240
|
+
resp ||= @klass.human_attribute_name(name)
|
241
|
+
end
|
242
|
+
|
243
|
+
#
|
244
|
+
# インデックス情報の取得
|
245
|
+
#
|
246
|
+
# add_index :articles, :name #=> "I"
|
247
|
+
# add_index :articles, :name, :unique => true #=> "UI"
|
248
|
+
#
|
249
|
+
def index_info(column)
|
250
|
+
indexes = @klass.connection.indexes(@klass.table_name)
|
251
|
+
# 関係するインデックスに絞る
|
252
|
+
indexes2 = indexes.find_all {|e| e.columns.include?(column.name) }
|
253
|
+
indexes2.collect {|e|
|
254
|
+
mark = ""
|
255
|
+
# そのインデックスは何番目にあるかを調べる
|
256
|
+
mark << ("A".."Z").to_a.at(indexes.index(e)).to_s
|
257
|
+
# ユニークなら「!」
|
258
|
+
if e.unique
|
259
|
+
mark << "!"
|
260
|
+
end
|
261
|
+
# mark << e.columns.size.to_s # 1なら単独、2ならペア、3ならトリプル指定みたいなのわかる
|
262
|
+
mark
|
263
|
+
}.join(" ")
|
264
|
+
end
|
265
|
+
|
266
|
+
#
|
267
|
+
# 指定のカラムは何かのインデックスに含まれているか?
|
268
|
+
#
|
269
|
+
def index_column?(column)
|
270
|
+
indexes = @klass.connection.indexes(@klass.table_name)
|
271
|
+
indexes.any?{|e|e.columns.include?(column.name)}
|
272
|
+
end
|
273
|
+
|
274
|
+
#
|
275
|
+
# belongs_to のカラムか?
|
276
|
+
#
|
277
|
+
def belongs_to_column?(column)
|
278
|
+
@klass.reflections.any? do |key, reflection|
|
279
|
+
if reflection.macro == :belongs_to
|
280
|
+
if reflection.respond_to?(:foreign_key)
|
281
|
+
reflection.foreign_key.to_s == column.name
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
#
|
288
|
+
# 指定のカラムがインデックスを貼るべきかどうかを表示する
|
289
|
+
#
|
290
|
+
def index_check(column)
|
291
|
+
if column.name.match(/(\w+)_id\z/) || belongs_to_column?(column)
|
292
|
+
# belongs_to :xxx, :polymorphic => true の場合は xxx_id と xxx_type のペアでインデックスを貼る
|
293
|
+
if (md = column.name.match(/(\w+)_id\z/)) && (type_column = @klass.columns_hash["#{md.captures.first}_type"])
|
294
|
+
unless index_column?(column) && index_column?(type_column)
|
295
|
+
memo_puts "【警告:インデックス欠如】create_#{@klass.table_name} マイグレーションに add_index :#{@klass.table_name}, [:#{column.name}, :#{type_column.name}] を追加してください"
|
296
|
+
end
|
297
|
+
else
|
298
|
+
unless index_column?(column)
|
299
|
+
memo_puts "【警告:インデックス欠如】create_#{@klass.table_name} マイグレーションに add_index :#{@klass.table_name}, :#{column.name} を追加してください"
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def memo_puts(str)
|
306
|
+
if @options[:debug]
|
307
|
+
Aam.logger.debug str if Aam.logger
|
308
|
+
end
|
309
|
+
@memos << str
|
310
|
+
nil
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|