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
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ class RenameProducts < ActiveRecord::Migration
3
+ def self.up
4
+ rename_table "products", "product_names"
5
+ end
6
+
7
+ def self.down
8
+ rename_table "product_names", "products"
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ class RenameProductsAgain < ActiveRecord::Migration
3
+ def self.up
4
+ rename_table "product_names", "products"
5
+ end
6
+
7
+ def self.down
8
+ rename_table "products", "product_names"
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ class RemovePrice < ActiveRecord::Migration
3
+ def self.up
4
+ remove_column "products", "price"
5
+ end
6
+
7
+ def self.down
8
+ add_column "products", "price", :integer, :comment => "価格"
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ class ChangeProductsName < ActiveRecord::Migration
3
+ def self.up
4
+ change_column "products", 'name', :string, :limit => 50
5
+ end
6
+
7
+ def self.down
8
+ change_column "products", 'name', :string
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ class ChangeProductsNameWithComment < ActiveRecord::Migration
3
+ def self.up
4
+ change_column "products", 'name', :string, :limit => 100, :comment => "名称"
5
+ end
6
+
7
+ def self.down
8
+ change_column "products", 'name', :string, :limit => 50, :comment => "商品名"
9
+ end
10
+ end
@@ -0,0 +1,2 @@
1
+ class Product < ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class ProductName < ActiveRecord::Base
2
+ end
data/spec/schema.rb ADDED
@@ -0,0 +1,2 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ end
@@ -0,0 +1,74 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.join(File.dirname(__FILE__), 'spec_helper')
3
+
4
+ describe ActiveRecord::SchemaDumper do
5
+
6
+ IGNORED_TABLES = %w(schema_migrations)
7
+
8
+ before(:each) do
9
+ SchemaComments.yaml_path = File.expand_path(File.join(File.dirname(__FILE__), 'schema_comments.yml'))
10
+ FileUtils.rm(SchemaComments.yaml_path, :verbose => true) if File.exist?(SchemaComments.yaml_path)
11
+
12
+ (ActiveRecord::Base.connection.tables - IGNORED_TABLES).each do |t|
13
+ ActiveRecord::Base.connection.drop_table(t) rescue nil
14
+ end
15
+ ActiveRecord::Base.connection.initialize_schema_migrations_table
16
+ ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
17
+ end
18
+
19
+ it "dump" do
20
+ (ActiveRecord::Base.connection.tables - %w(schema_migrations)).should == []
21
+
22
+ migration_path = File.join(MIGRATIONS_ROOT, 'valid')
23
+ Dir.glob('*.rb').each do |file|
24
+ require(file) if /^\d+?_.*/ =~ file
25
+ end
26
+
27
+ Product.reset_table_comments
28
+ Product.reset_column_comments
29
+
30
+ ActiveRecord::Migrator.up(migration_path, 1)
31
+ ActiveRecord::Migrator.current_version.should == 1
32
+
33
+ ActiveRecord::Base.export_i18n_models.keys.include?('product').should == true
34
+ ActiveRecord::Base.export_i18n_models['product'].should == '商品'
35
+
36
+ ActiveRecord::Base.export_i18n_attributes.keys.include?('product').should == true
37
+ ActiveRecord::Base.export_i18n_attributes['product'].should == {
38
+ 'product_type_cd' => '種別コード',
39
+ "price" => "価格",
40
+ "name" => "商品名",
41
+ "created_at" => "登録日時",
42
+ "updated_at" => "更新日時"
43
+ }
44
+
45
+ dest = StringIO.new
46
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, dest)
47
+ dest.rewind
48
+ dest.read.should == <<EOS
49
+ # This file is auto-generated from the current state of the database. Instead of editing this file,
50
+ # please use the migrations feature of Active Record to incrementally modify your database, and
51
+ # then regenerate this schema definition.
52
+ #
53
+ # Note that this schema.rb definition is the authoritative source for your database schema. If you need
54
+ # to create the application database on another system, you should be using db:schema:load, not running
55
+ # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
56
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
57
+ #
58
+ # It's strongly recommended to check this file into your version control system.
59
+
60
+ ActiveRecord::Schema.define(:version => 1) do
61
+
62
+ create_table "products", :force => true, :comment => '商品' do |t|
63
+ t.string "product_type_cd", :comment => "種別コード"
64
+ t.integer "price", :comment => "価格"
65
+ t.string "name", :comment => "商品名"
66
+ t.datetime "created_at", :comment => "登録日時"
67
+ t.datetime "updated_at", :comment => "更新日時"
68
+ end
69
+
70
+ end
71
+ EOS
72
+ end
73
+
74
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -0,0 +1,46 @@
1
+ $KCODE='u'
2
+
3
+ ENV['RAILS_ENV'] ||= 'test'
4
+ unless defined?(RAILS_ENV)
5
+ FIXTURES_ROOT = File.join(File.dirname(__FILE__), 'fixtures') unless defined?(FIXTURES_ROOT)
6
+
7
+ RAILS_ENV = 'test'
8
+ RAILS_ROOT = File.dirname(__FILE__) unless defined?(RAILS_ROOT)
9
+
10
+ require 'rubygems'
11
+ require 'spec'
12
+
13
+ require 'active_support'
14
+ require 'active_record'
15
+ # require 'action_mailer'
16
+ require 'action_controller'
17
+ require 'action_view'
18
+ require 'initializer'
19
+
20
+ require 'yaml'
21
+ begin
22
+ require 'yaml_waml'
23
+ rescue
24
+ $stderr.puts "yaml_waml not found. You should [sudo] gem install kakutani-yaml_waml"
25
+ end
26
+
27
+ config = YAML.load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
28
+ ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), 'debug.log'))
29
+ ActionController::Base.logger = ActiveRecord::Base.logger
30
+ ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
31
+
32
+
33
+ load(File.join(File.dirname(__FILE__), 'schema.rb'))
34
+
35
+ %w(resources/models).each do |path|
36
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), path)
37
+ ActiveSupport::Dependencies.load_paths << File.join(File.dirname(__FILE__), path)
38
+ end
39
+ Dir.glob("resources/**/*.rb") do |filename|
40
+ require filename
41
+ end
42
+
43
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
44
+ require File.join(File.dirname(__FILE__), '..', 'init')
45
+ end
46
+
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.join(File.dirname(__FILE__), 'spec_helper')
3
+
4
+ describe SchemaComments::SchemaComment do
5
+
6
+ before(:each) do
7
+ SchemaComments.yaml_path = File.join(File.dirname(__FILE__), 'human_readable_schema_comments.yml')
8
+ FileUtils.rm(SchemaComments.yaml_path, :verbose => true) if File.exist?(SchemaComments.yaml_path)
9
+
10
+ (ActiveRecord::Base.connection.tables - IGNORED_TABLES).each do |t|
11
+ ActiveRecord::Base.connection.drop_table(t) rescue nil
12
+ end
13
+ ActiveRecord::Base.connection.initialize_schema_migrations_table
14
+ ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
15
+ end
16
+
17
+ it "should export human readable yaml" do
18
+ ActiveRecord::Schema.define(:version => 0) do
19
+ create_table(:person, :comment => '人') do |t|
20
+ t.string :name, :comment => '名前'
21
+ end
22
+
23
+ create_table(:addresses, :comment => '住所') do |t|
24
+ t.integer :person_id, :comment => '人'
25
+ t.text :descriptions, :comment => '記述'
26
+ end
27
+
28
+ create_table(:emails, :comment => 'メール') do |t|
29
+ t.integer :person_id, :comment => '人'
30
+ t.string :address, :comment => 'アドレス'
31
+ end
32
+ end
33
+
34
+ File.read(SchemaComments.yaml_path).split(/$/).map(&:strip).should == %{
35
+ ---
36
+ table_comments:
37
+ addresses: "住所"
38
+ emails: "メール"
39
+ person: "人"
40
+ column_comments:
41
+ addresses:
42
+ person_id: "人"
43
+ descriptions: "記述"
44
+ emails:
45
+ person_id: "人"
46
+ address: "アドレス"
47
+ person:
48
+ name: "名前"
49
+ }.split(/$/).map(&:strip)
50
+ end
51
+
52
+ end
@@ -0,0 +1,12 @@
1
+ # Original file
2
+ # http://github.com/rotuka/annotate_models/blob/d2afee82020dbc592b147d92f9beeadbf665a9e0/tasks/annotate_models_tasks.rake
3
+ namespace :db do
4
+ desc "Add schema information (as comments) to model files"
5
+ task :annotate => :environment do
6
+ require File.join(File.dirname(__FILE__), "../lib/annotate_models.rb")
7
+ AnnotateModels.do_annotations
8
+ end
9
+
10
+ desc "Updates database (migrate and annotate models)"
11
+ task :update => %w(migrate annotate)
12
+ end
@@ -0,0 +1,204 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'yaml'
3
+ require 'yaml_waml'
4
+ require 'activerecord'
5
+
6
+ # テストを実行する際はschema_commentsのschema_comments.ymlへの出力を抑制します。
7
+ namespace :db do
8
+ Rake.application.send(:eval, "@tasks.delete('db:migrate')")
9
+ desc "Migrate the database through scripts in db/migrate and update db/schema.rb by invoking db:schema:dump. Target specific version with VERSION=x. Turn off output with VERBOSE=false."
10
+ task :migrate => :environment do
11
+ SchemaComments::SchemaComment.yaml_access do
12
+ ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
13
+ ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
14
+ SchemaComments.quiet = true
15
+ Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
16
+ end
17
+ end
18
+
19
+ Rake.application.send(:eval, "@tasks.delete('db:rollback')")
20
+ desc 'Rolls the schema back to the previous version. Specify the number of steps with STEP=n'
21
+ task :rollback => :environment do
22
+ SchemaComments::SchemaComment.yaml_access do
23
+ step = ENV['STEP'] ? ENV['STEP'].to_i : 1
24
+ ActiveRecord::Migrator.rollback('db/migrate/', step)
25
+ SchemaComments.quiet = true
26
+ Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
27
+ end
28
+ end
29
+
30
+ namespace :migrate do
31
+ desc 'Runs the "up" for a given migration VERSION.'
32
+ task :up => :environment do
33
+ SchemaComments::SchemaComment.yaml_access do
34
+ version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
35
+ raise "VERSION is required" unless version
36
+ ActiveRecord::Migrator.run(:up, "db/migrate/", version)
37
+ SchemaComments.quiet = true
38
+ Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
39
+ end
40
+ end
41
+
42
+ desc 'Runs the "down" for a given migration VERSION.'
43
+ task :down => :environment do
44
+ SchemaComments::SchemaComment.yaml_access do
45
+ version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
46
+ raise "VERSION is required" unless version
47
+ ActiveRecord::Migrator.run(:down, "db/migrate/", version)
48
+ SchemaComments.quiet = true
49
+ Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
50
+ end
51
+ end
52
+ end
53
+
54
+ namespace :test do
55
+ Rake.application.send(:eval, "@tasks.delete('db:test:prepare')")
56
+ desc 'Check for pending migrations and load the test schema'
57
+ task :prepare => 'db:abort_if_pending_migrations' do
58
+ SchemaComments::SchemaComment.yaml_access do
59
+ SchemaComments.quiet = true
60
+ if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
61
+ Rake::Task[{ :sql => "db:test:clone_structure", :ruby => "db:test:load"
62
+ }[ActiveRecord::Base.schema_format]].invoke
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+
71
+
72
+ class ActiveRecord::Base
73
+ class << self
74
+ attr_accessor_with_default :ignore_pattern_to_export_i18n, /\(\(\(.*\)\)\)/
75
+
76
+ def export_i18n_models
77
+ subclasses = ActiveRecord::Base.send(:subclasses).select do |klass|
78
+ (klass != SchemaComments::SchemaComment) and
79
+ klass.respond_to?(:table_exists?) and klass.table_exists?
80
+ end
81
+ result = subclasses.inject({}) do |d, m|
82
+ comment = (m.table_comment || '').dup
83
+ comment.gsub!(ignore_pattern_to_export_i18n, '') if ignore_pattern_to_export_i18n
84
+ # テーブル名(複数形)をモデル名(単数形)に
85
+ model_name = (comment.scan(/\[\[\[(?:model|class)(?:_name)?:\s*?([^\s]+?)\s*?\]\]\]/).flatten.first || m.name).underscore
86
+ comment.gsub!(/\[\[\[.*?\]\]\]/)
87
+ d[model_name] = comment
88
+ d
89
+ end
90
+ result.instance_eval do
91
+ def each_with_order(*args, &block)
92
+ self.keys.sort.each do |key|
93
+ yield(key, self[key])
94
+ end
95
+ end
96
+ alias :each_without_order :each
97
+ alias :each :each_with_order
98
+ end
99
+ result
100
+ end
101
+
102
+ def export_i18n_attributes
103
+ subclasses = ActiveRecord::Base.send(:subclasses).select do |klass|
104
+ (klass != SchemaComments::SchemaComment) and
105
+ klass.respond_to?(:table_exists?) and klass.table_exists?
106
+ end
107
+ result = subclasses.inject({}) do |d, m|
108
+ attrs = {}
109
+ m.columns.each do |col|
110
+ next if col.name == 'id'
111
+ comment = (col.comment || '').dup
112
+ comment.gsub!(ignore_pattern_to_export_i18n, '') if ignore_pattern_to_export_i18n
113
+
114
+ # カラム名を属性名に
115
+ attr_name = (comment.scan(/\[\[\[(?:attr|attribute)(?:_name)?:\s*?([^\s]+?)\s*?\]\]\]/).flatten.first || col.name)
116
+ comment.gsub!(/\[\[\[.*?\]\]\]/)
117
+ attrs[attr_name] = comment
118
+ end
119
+
120
+ column_names = m.columns.map(&:name) - ['id']
121
+ column_order_modeule = Module.new do
122
+ def each_with_column_order(*args, &block)
123
+ @column_names.each do |column_name|
124
+ yield(column_name, self[column_name])
125
+ end
126
+ end
127
+
128
+ def self.extended(obj)
129
+ obj.instance_eval do
130
+ alias :each_without_column_order :each
131
+ alias :each :each_with_column_order
132
+ end
133
+ end
134
+ end
135
+ attrs.instance_variable_set(:@column_names, column_names)
136
+ attrs.extend(column_order_modeule)
137
+
138
+ # テーブル名(複数形)をモデル名(単数形)に
139
+ model_name = ((m.table_comment || '').scan(/\[\[\[(?:model|class)(?:_name)?:\s*?([^\s]+?)\s*?\]\]\]/).flatten.first || m.name).underscore
140
+ d[model_name] = attrs
141
+ d
142
+ end
143
+
144
+ result.instance_eval do
145
+ def each_with_order(*args, &block)
146
+ self.keys.sort.each do |key|
147
+ yield(key, self[key])
148
+ end
149
+ end
150
+ alias :each_without_order :each
151
+ alias :each :each_with_order
152
+ end
153
+ result
154
+ end
155
+ end
156
+ end
157
+
158
+ namespace :i18n do
159
+ namespace :schema_comments do
160
+ task :load_all_models => :environment do
161
+ Dir.glob(File.join(RAILS_ROOT, 'app', 'models', '**', '*.rb')) do |file_name|
162
+ require file_name
163
+ end
164
+ end
165
+
166
+ desc "Export i18n model resources from schema_comments. you can set locale with environment variable LOCALE"
167
+ task :export_models => :"i18n:schema_comments:load_all_models" do
168
+ locale = (ENV['LOCALE'] || I18n.locale).to_s
169
+ obj = {locale => {'activerecord' => {'models' => ActiveRecord::Base.export_i18n_models}}}
170
+ puts YAML.dump(obj)
171
+ end
172
+
173
+ desc "Export i18n attributes resources from schema_comments. you can set locale with environment variable LOCALE"
174
+ task :export_attributes => :"i18n:schema_comments:load_all_models" do
175
+ locale = (ENV['LOCALE'] || I18n.locale).to_s
176
+ obj = {locale => {'activerecord' => {'attributes' => ActiveRecord::Base.export_i18n_attributes}}}
177
+ puts YAML.dump(obj)
178
+ end
179
+
180
+ desc "update i18n YAML. you can set locale with environment variable LOCALE"
181
+ task :update_config_locale => :"i18n:schema_comments:load_all_models" do
182
+ require 'yaml/store'
183
+ locale = (ENV['LOCALE'] || I18n.locale).to_s
184
+ path = (ENV['YAML_PATH'] || File.join(RAILS_ROOT, "config/locales/#{locale}.yml"))
185
+ print "updating #{path}..."
186
+
187
+ begin
188
+ db = YAML::Store.new(path)
189
+ db.transaction do
190
+ locale = db[locale] ||= {}
191
+ activerecord = locale['activerecord'] ||= {}
192
+ activerecord['models'] = ActiveRecord::Base.export_i18n_models
193
+ activerecord['attributes'] = ActiveRecord::Base.export_i18n_attributes
194
+ end
195
+ puts "Complete!"
196
+ rescue Exception
197
+ puts "Failure!!!"
198
+ puts $!.to_s
199
+ puts " " << $!.backtrace.join("\n ")
200
+ raise
201
+ end
202
+ end
203
+ end
204
+ end