schema_comments 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +4 -0
  2. data/LICENSE.txt +60 -0
  3. data/README +149 -0
  4. data/Rakefile +46 -0
  5. data/VERSION +1 -0
  6. data/autotest/discover.rb +10 -0
  7. data/init.rb +4 -0
  8. data/lib/annotate_models.rb +224 -0
  9. data/lib/schema_comments/base.rb +72 -0
  10. data/lib/schema_comments/connection_adapters.rb +170 -0
  11. data/lib/schema_comments/migration.rb +20 -0
  12. data/lib/schema_comments/migrator.rb +20 -0
  13. data/lib/schema_comments/schema.rb +20 -0
  14. data/lib/schema_comments/schema_comment.rb +195 -0
  15. data/lib/schema_comments/schema_dumper.rb +160 -0
  16. data/lib/schema_comments.rb +53 -0
  17. data/spec/.gitignore +3 -0
  18. data/spec/annotate_models_spec.rb +56 -0
  19. data/spec/database.yml +13 -0
  20. data/spec/fixtures/.gitignore +0 -0
  21. data/spec/i18n_export_spec.rb +48 -0
  22. data/spec/migration_spec.rb +96 -0
  23. data/spec/migrations/valid/001_create_products.rb +17 -0
  24. data/spec/migrations/valid/002_rename_products.rb +10 -0
  25. data/spec/migrations/valid/003_rename_products_again.rb +10 -0
  26. data/spec/migrations/valid/004_remove_price.rb +10 -0
  27. data/spec/migrations/valid/005_change_products_name.rb +10 -0
  28. data/spec/migrations/valid/006_change_products_name_with_comment.rb +10 -0
  29. data/spec/resources/models/product.rb +2 -0
  30. data/spec/resources/models/product_name.rb +2 -0
  31. data/spec/schema.rb +2 -0
  32. data/spec/schema_dumper_spec.rb +74 -0
  33. data/spec/spec.opts +6 -0
  34. data/spec/spec_helper.rb +46 -0
  35. data/spec/yaml_export_spec.rb +52 -0
  36. data/tasks/annotate_models_tasks.rake +12 -0
  37. data/tasks/schema_comments.rake +204 -0
  38. metadata +115 -0
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.log
2
+ *~
3
+ /pkg
4
+ /selectable_attr_test.sqlite3.db
data/LICENSE.txt ADDED
@@ -0,0 +1,60 @@
1
+ Ruby License
2
+ http://www.ruby-lang.org/en/LICENSE.txt
3
+
4
+ Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.co.jp>.
5
+ You can redistribute it and/or modify it under either the terms of the GPL
6
+ (see COPYING.txt file), or the conditions below:
7
+
8
+ 1. You may make and give away verbatim copies of the source form of the
9
+ software without restriction, provided that you duplicate all of the
10
+ original copyright notices and associated disclaimers.
11
+
12
+ 2. You may modify your copy of the software in any way, provided that
13
+ you do at least ONE of the following:
14
+
15
+ a) place your modifications in the Public Domain or otherwise
16
+ make them Freely Available, such as by posting said
17
+ modifications to Usenet or an equivalent medium, or by allowing
18
+ the author to include your modifications in the software.
19
+
20
+ b) use the modified software only within your corporation or
21
+ organization.
22
+
23
+ c) rename any non-standard executables so the names do not conflict
24
+ with standard executables, which must also be provided.
25
+
26
+ d) make other distribution arrangements with the author.
27
+
28
+ 3. You may distribute the software in object code or executable
29
+ form, provided that you do at least ONE of the following:
30
+
31
+ a) distribute the executables and library files of the software,
32
+ together with instructions (in the manual page or equivalent)
33
+ on where to get the original distribution.
34
+
35
+ b) accompany the distribution with the machine-readable source of
36
+ the software.
37
+
38
+ c) give non-standard executables non-standard names, with
39
+ instructions on where to get the original software distribution.
40
+
41
+ d) make other distribution arrangements with the author.
42
+
43
+ 4. You may modify and include the part of the software into any other
44
+ software (possibly commercial). But some files in the distribution
45
+ are not written by the author, so that they are not under this terms.
46
+
47
+ They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
48
+ files under the ./missing directory. See each file for the copying
49
+ condition.
50
+
51
+ 5. The scripts and library files supplied as input to or produced as
52
+ output from the software do not automatically fall under the
53
+ copyright of the software, but belong to whomever generated them,
54
+ and may be sold commercially, and may be aggregated with this
55
+ software.
56
+
57
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
58
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
59
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
60
+ PURPOSE.
data/README ADDED
@@ -0,0 +1,149 @@
1
+ SchemaComments
2
+ ==========
3
+
4
+ == Install ==
5
+
6
+ === as a plugin
7
+ ruby script/plugin install git://github.com/akm/schema_comments.git
8
+
9
+ === as a gem
10
+ insert following line to config/environment.rb
11
+ config.gem 'schema_comments', :version => '0.1.0'
12
+ and
13
+ $ sudo rake gems:install
14
+
15
+ Or install gem manually
16
+
17
+ $ sudo gem install gemcutter
18
+ $ sudo gem tumble
19
+ $ sudo gem install schema_comments
20
+
21
+ == Configuration
22
+ If you install schema_comments as a gem, must create config/initializers/schema_comments.rb like this:
23
+ require 'schema_comments'
24
+ SchemaComments.setup
25
+
26
+
27
+ == overview ==
28
+ schema_commentsプラグインを使うと、テーブルとカラムにコメントを記述することができます。
29
+
30
+ class CreateProducts < ActiveRecord::Migration
31
+ def self.up
32
+ create_table "products", :comment => '商品' do |t|
33
+ t.string "product_type_cd", :comment => '種別コード'
34
+ t.integer "price", :comment => "価格"
35
+ t.string "name", :comment => "商品名"
36
+ t.datetime "created_at", :comment => "登録日時"
37
+ t.datetime "updated_at", :comment => "更新日時"
38
+ end
39
+ end
40
+
41
+ def self.down
42
+ drop_table "products"
43
+ end
44
+ end
45
+
46
+ こんな感じ。
47
+
48
+ でこのようなマイグレーションを実行すると、db/schema.rb には、
49
+ コメントが設定されているテーブル、カラムは以下のように出力されます。
50
+
51
+ ActiveRecord::Schema.define(:version => 0) do
52
+ create_table "products", :force => true, :comment => '商品' do |t|
53
+ t.string "product_type_cd", :comment => '種別コード'
54
+ t.integer "price", :comment => "価格"
55
+ t.string "name", :comment => "商品名"
56
+ t.datetime "created_at", :comment => "登録日時"
57
+ t.datetime "updated_at", :comment => "更新日時"
58
+ end
59
+ end
60
+
61
+
62
+ コメントは、以下のメソッドで使用することが可能です。
63
+
64
+ columns, create_table, drop_table, rename_table
65
+ remove_column, add_column, change_column
66
+
67
+
68
+ == コメントはどこに保存されるのか ==
69
+ db/schema_comments.yml にYAML形式で保存されます。
70
+ あまり推奨しませんが、もしマイグレーションにコメントを記述するのを忘れてしまった場合、db/schema_comments.yml
71
+ を直接編集した後、rake db:schema:dumpやマイグレーションを実行すると、db/schema.rbのコメントに反映されます。
72
+
73
+
74
+ == I18nへの対応 ==
75
+ rake i18n:schema_comments:update_config_localeタスクを実行すると、i18n用のYAMLを更新できます。
76
+
77
+ rake i18n:schema_comments:update_config_locale LOCALE=ja でデフォルトではconfig/locales/ja.ymlを更新します。
78
+
79
+ 毎回LOCALEを指定するのが面倒な場合は、config/initializers/locale.rb に
80
+ I18n.default_locale = 'ja'
81
+ という記述を追加しておくと良いでしょう。
82
+
83
+ また出力先のYAMLのPATHを指定したい場合、YAML_PATHで指定が可能です。
84
+ rake i18n:schema_comments:update_config_locale LOCALE=ja YAML_PATH=/path/to/yaml
85
+
86
+ === コメント内コメント ===
87
+ コメント中の ((( から ))) は反映されませんので、モデル名/属性名に含めたくない箇所は ((( と ))) で括ってください。
88
+ ((( ))) と同様に[[[ ]]]も使用できます。
89
+ 例えば以下のようにdb/schema.rbに出力されている場合、
90
+ ActiveRecord::Schema.define(:version => 0) do
91
+ create_table "products", :force => true, :comment => '商品' do |t|
92
+ t.string "product_type_cd", :comment => '種別コード(((01:書籍, 02:靴, 03:パソコン)))'
93
+ t.integer "price", :comment => "価格"
94
+ t.string "name", :comment => "商品名"
95
+ t.datetime "created_at", :comment => "登録日時"
96
+ t.datetime "updated_at", :comment => "更新日時"
97
+ end
98
+ end
99
+
100
+ rake i18n:schema_comments:update_config_locale LOCALE=ja
101
+ とすると、以下のように出力されます。
102
+ ja:
103
+ activerecord:
104
+ attributes:
105
+ product:
106
+ product_type_cd: "種別コード"
107
+ price: "価格"
108
+ name: "商品名"
109
+ created_at: "登録日時"
110
+ updated_at: "更新日時"
111
+
112
+
113
+
114
+ == MySQLのビュー ==
115
+ MySQLのビューを使用した場合、元々MySQLではSHOW TABLES でビューも表示してしまうため、
116
+ ビューはテーブルとしてSchemaDumperに認識され、development環境ではMySQLのビューとして作成されているのに、
117
+ test環境ではテーブルとして作成されてしまい、テストが正しく動かないことがあります。
118
+ これを避けるため、schema_commentsでは、db/schema.rbを出力する際、テーブルに関する記述の後に、CREATE VIEWを行う記述を追加します。
119
+
120
+
121
+ == annotate_models ==
122
+ rake db:annotate で以下のようなコメントを、モデル、テスト、フィクスチャといったモデルに関係の強いファイルの
123
+ 先頭に追加します。
124
+ # == Schema Info ==
125
+ #
126
+ # Schema version: 20090721185959
127
+ #
128
+ # Table name: books # 書籍
129
+ #
130
+ # id :integer not null, primary key
131
+ # title :string(100) not null # タイトル
132
+ # size :integer not null, default(1) # 判型
133
+ # price :decimal(17, 14) not null, default(0.0) # 価格
134
+ # created_at :datetime # 登録日時
135
+ # updated_at :datetime # 更新日時
136
+ #
137
+ # =================
138
+ #
139
+
140
+ また、rake db:updateで、rake db:migrateとrake db:annotateを実行します。
141
+
142
+ annotate_modelsは、達人プログラマーのDave Thomasさんが公開しているプラグインです。
143
+ http://repo.pragprog.com/svn/Public/plugins/annotate_models/
144
+
145
+ 本プラグインでは、それを更に拡張したDave Boltonさんのプラグイン(
146
+ http://github.com/rotuka/annotate_models )をベースに拡張を加えています。
147
+
148
+
149
+ Copyright (c) 2008 Takeshi AKIMA, released under the Ruby License
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "schema_comments"
8
+ gem.summary = "schema_comments generates extra methods dynamically"
9
+ gem.description = "schema_comments generates extra methods dynamically for attribute which has options"
10
+ gem.email = "akm2000@gmail.com"
11
+ gem.homepage = "http://github.com/akm/schema_comments"
12
+ gem.authors = ["akimatter"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "schema_comments #{version}"
43
+ rdoc.rdoc_files.include('README.rdoc')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ rdoc.options = ["--charset", "utf-8", "--line-numbers"]
46
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ $KCODE='u'
3
+
4
+ # Autotestを実行するためのスクリプトです。
5
+ # see http://rails.aizatto.com/2007/11/19/autotest-ing-your-rails-plugin/
6
+ # $:.push(File.join(File.dirname(__FILE__), %w[.. .. rspec]))
7
+
8
+ Autotest.add_discovery do
9
+ "rspec"
10
+ end
data/init.rb ADDED
@@ -0,0 +1,4 @@
1
+ unless ENV['SCHEMA_COMMENTS_DISABLED']
2
+ require 'schema_comments'
3
+ SchemaComments.setup
4
+ end
@@ -0,0 +1,224 @@
1
+
2
+ # fork from
3
+ # http://github.com/rotuka/annotate_models/blob/d2afee82020dbc592b147d92f9beeadbf665a9e0/lib/annotate_models.rb
4
+
5
+ require "config/environment" if File.exist?("config/environment")
6
+
7
+ MODEL_DIR = File.join(RAILS_ROOT, "app/models" )
8
+ UNIT_TEST_DIR = File.join(RAILS_ROOT, "test/unit" )
9
+ SPEC_MODEL_DIR = File.join(RAILS_ROOT, "spec/models")
10
+ FIXTURES_DIR = File.join(RAILS_ROOT, "test/fixtures")
11
+ SPEC_FIXTURES_DIR = File.join(RAILS_ROOT, "spec/fixtures")
12
+
13
+ module AnnotateModels
14
+
15
+ PREFIX_AT_BOTTOM = "== Schema Info"
16
+ SUFFIX_AT_BOTTOM = ""
17
+ PREFIX_ON_TOP = "== Schema Info =="
18
+ SUFFIX_ON_TOP = "=================\n# "
19
+
20
+ # ENV options
21
+ {:position => 'top', :model_dir => MODEL_DIR}.each do |name, default_value|
22
+ mattr_accessor name.to_sym
23
+ self.send("#{name}=", ENV[name.to_s.upcase] || default_value)
24
+ end
25
+
26
+ def self.models
27
+ (ENV['MODELS'] || '').split(',')
28
+ end
29
+
30
+ def self.sort_columns
31
+ ENV['SORT'] =~ /yes|on|true/i
32
+ end
33
+
34
+ def self.output_prefix
35
+ bottom? ? PREFIX_AT_BOTTOM : PREFIX_ON_TOP
36
+ end
37
+
38
+ def self.output_suffix
39
+ bottom? ? SUFFIX_AT_BOTTOM : SUFFIX_ON_TOP
40
+ end
41
+
42
+ def self.bottom?
43
+ self.position =~ /bottom/i
44
+ end
45
+
46
+ def self.separate?
47
+ ENV['SEPARATE'] =~ /yes|on|true/i
48
+ end
49
+
50
+
51
+ # Simple quoting for the default column value
52
+ def self.quote(value)
53
+ case value
54
+ when NilClass then "NULL"
55
+ when TrueClass then "TRUE"
56
+ when FalseClass then "FALSE"
57
+ when Float, Fixnum, Bignum then value.to_s
58
+ # BigDecimals need to be output in a non-normalized form and quoted.
59
+ when BigDecimal then value.to_s('F')
60
+ else
61
+ value.inspect
62
+ end
63
+ end
64
+
65
+ # Use the column information in an ActiveRecord class
66
+ # to create a comment block containing a line for
67
+ # each column. The line contains the column name,
68
+ # the type (and length), and any optional attributes
69
+ def self.get_schema_info(klass)
70
+ table_info = "# Table name: #{klass.table_name}"
71
+ table_info << " # #{klass.table_comment}" unless klass.table_comment.blank?
72
+ table_info << "\n#\n"
73
+ max_size = klass.column_names.collect{|name| name.size}.max + 1
74
+
75
+ columns = klass.columns
76
+
77
+ cols = if self.sort_columns
78
+ pk = columns.find_all { |col| col.name == klass.primary_key }.flatten
79
+ assoc = columns.find_all { |col| col.name.match(/_id$/) }.sort_by(&:name)
80
+ dates = columns.find_all { |col| col.name.match(/_on$/) }.sort_by(&:name)
81
+ times = columns.find_all { |col| col.name.match(/_at$/) }.sort_by(&:name)
82
+ pk + assoc + (columns - pk - assoc - times - dates).compact.sort_by(&:name) + dates + times
83
+ else
84
+ columns
85
+ end
86
+
87
+ col_lines = append_comments(cols.map{|col| [col, annotate_column(col, klass, max_size)]})
88
+ cols_text = col_lines.join("\n")
89
+
90
+ result = "# #{self.output_prefix}\n# \n# Schema version: #{get_schema_version}\n#\n"
91
+ result << table_info
92
+ result << cols_text
93
+ result << "\n# \n# #{self.output_suffix}" unless self.output_suffix.blank?
94
+ result << "\n"
95
+ result
96
+ end
97
+
98
+ def self.annotate_column(col, klass, max_size)
99
+ attrs = []
100
+ attrs << "not null" unless col.null
101
+ attrs << "default(#{quote(col.default)})" if col.default
102
+ attrs << "primary key" if col.name == klass.primary_key
103
+
104
+ col_type = col.type.to_s
105
+ if col_type == "decimal"
106
+ col_type << "(#{col.precision}, #{col.scale})"
107
+ else
108
+ col_type << "(#{col.limit})" if col.limit
109
+ end
110
+ sprintf("# %-#{max_size}s:%-15s %s", col.name, col_type, attrs.join(", ")).rstrip
111
+ end
112
+
113
+ def self.append_comments(col_and_lines)
114
+ max_length = col_and_lines.map{|(col, line)| line.length}.max
115
+ col_and_lines.map do |(col, line)|
116
+ if col.comment.blank?
117
+ line
118
+ else
119
+ "%-#{max_length}s # %s" % [line, col.comment]
120
+ end
121
+ end
122
+ end
123
+
124
+ # Add a schema block to a file. If the file already contains
125
+ # a schema info block (a comment starting
126
+ # with "Schema as of ..."), remove it first.
127
+ # Mod to write to the end of the file
128
+ def self.annotate_one_file(file_name, info_block)
129
+ if File.exist?(file_name)
130
+ content = File.read(file_name)
131
+
132
+ encoding_comment = content.scan(/^\#\s*-\*-(.+?)-\*-/).flatten.first
133
+ content.sub!(/^\#\s*-\*-(.+?)-\*-/, '')
134
+
135
+ # Remove old schema info
136
+ content.sub!(/(\n)*^# #{PREFIX_ON_TOP}.*?\n(#.*\n)*# #{SUFFIX_ON_TOP}/, '')
137
+ content.sub!(/(\n)*^# #{PREFIX_AT_BOTTOM}.*?\n(#.*\n)*#.*(\n)*/, '')
138
+ content.sub!(/^[\n\s]*/, '')
139
+
140
+ # Write it back
141
+ File.open(file_name, "w") do |f|
142
+ f.print "# -*- #{encoding_comment.strip} -*-\n\n" unless encoding_comment.blank?
143
+ if self.bottom?
144
+ f.print content
145
+ f.print "\n\n"
146
+ f.print info_block
147
+ else
148
+ f.print info_block
149
+ f.print "\n" if self.separate?
150
+ f.print content
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+
157
+ # Given the name of an ActiveRecord class, create a schema
158
+ # info block (basically a comment containing information
159
+ # on the columns and their types) and put it at the front
160
+ # of the model and fixture source files.
161
+ def self.annotate(klass)
162
+ info = get_schema_info(klass)
163
+ model_name = klass.name.underscore
164
+ fixtures_name = "#{klass.table_name}.yml"
165
+
166
+ [
167
+ File.join(self.model_dir, "#{model_name}.rb"), # model
168
+ File.join(UNIT_TEST_DIR, "#{model_name}_test.rb"), # test
169
+ File.join(FIXTURES_DIR, fixtures_name), # fixture
170
+ File.join(SPEC_MODEL_DIR, "#{model_name}_spec.rb"), # spec
171
+ File.join(SPEC_FIXTURES_DIR, fixtures_name), # spec fixture
172
+ File.join(RAILS_ROOT, 'test', 'factories.rb'), # factories file
173
+ File.join(RAILS_ROOT, 'spec', 'factories.rb'), # factories file
174
+ ].each { |file| annotate_one_file(file, info) }
175
+ end
176
+
177
+ # Return a list of the model files to annotate. If we have
178
+ # command line arguments, they're assumed to be either
179
+ # the underscore or CamelCase versions of model names.
180
+ # Otherwise we take all the model files in the
181
+ # app/models directory.
182
+ def self.get_model_names
183
+ result = nil
184
+ if self.models.empty?
185
+ Dir.chdir(self.model_dir) do
186
+ result = Dir["**/*.rb"].map do |filename|
187
+ filename.sub(/\.rb$/, '').camelize
188
+ end
189
+ end
190
+ else
191
+ result = self.models.dup
192
+ end
193
+ result
194
+ end
195
+
196
+ # We're passed a name of things that might be
197
+ # ActiveRecord models. If we can find the class, and
198
+ # if its a subclass of ActiveRecord::Base,
199
+ # then pas it to the associated block
200
+ def self.do_annotations
201
+ annotated = self.get_model_names.inject([]) do |list, class_name|
202
+ begin
203
+ # klass = class_name.split('::').inject(Object){ |klass,part| klass.const_get(part) }
204
+ klass = class_name.constantize
205
+ if klass < ActiveRecord::Base && !klass.abstract_class?
206
+ list << class_name
207
+ self.annotate(klass)
208
+ end
209
+ rescue Exception => e
210
+ puts "Unable to annotate #{class_name}: #{e.message}"
211
+ end
212
+ list
213
+ end
214
+ puts "Annotated #{annotated.join(', ')}"
215
+ end
216
+
217
+ def self.get_schema_version
218
+ unless @schema_version
219
+ version = ActiveRecord::Migrator.current_version rescue 0
220
+ @schema_version = version > 0 ? version : ''
221
+ end
222
+ @schema_version
223
+ end
224
+ end