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