schema_comments 0.1.0

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 (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