hobo 0.5.3 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/bin/hobo +18 -4
  2. data/hobo_files/plugin/CHANGES.txt +511 -0
  3. data/hobo_files/plugin/README +8 -3
  4. data/hobo_files/plugin/Rakefile +81 -0
  5. data/hobo_files/plugin/generators/hobo/hobo_generator.rb +4 -4
  6. data/hobo_files/plugin/generators/hobo/templates/guest.rb +1 -1
  7. data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
  8. data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +16 -22
  9. data/hobo_files/plugin/generators/hobo_front_controller/templates/login.dryml +4 -6
  10. data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +6 -5
  11. data/hobo_files/plugin/generators/hobo_front_controller/templates/signup.dryml +4 -6
  12. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +237 -0
  13. data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +9 -0
  14. data/hobo_files/plugin/generators/hobo_model/USAGE +2 -3
  15. data/hobo_files/plugin/generators/hobo_model/hobo_model_generator.rb +1 -14
  16. data/hobo_files/plugin/generators/hobo_model/templates/fixtures.yml +1 -6
  17. data/hobo_files/plugin/generators/hobo_model/templates/model.rb +10 -4
  18. data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +7 -6
  19. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_base.css +68 -0
  20. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.css +93 -0
  21. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +11 -6
  22. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/plus.png +0 -0
  23. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +24 -14
  24. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +28 -44
  25. data/hobo_files/plugin/generators/hobo_user_model/USAGE +2 -12
  26. data/hobo_files/plugin/generators/hobo_user_model/hobo_user_model_generator.rb +1 -14
  27. data/hobo_files/plugin/generators/hobo_user_model/templates/fixtures.yml +0 -6
  28. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +8 -1
  29. data/hobo_files/plugin/init.rb +6 -2
  30. data/hobo_files/plugin/lib/active_record/has_many_association.rb +23 -12
  31. data/hobo_files/plugin/lib/extensions.rb +134 -40
  32. data/hobo_files/plugin/lib/extensions/test_case.rb +0 -1
  33. data/hobo_files/plugin/lib/hobo.rb +77 -46
  34. data/hobo_files/plugin/lib/hobo/authenticated_user.rb +24 -2
  35. data/hobo_files/plugin/lib/hobo/authentication_support.rb +2 -1
  36. data/hobo_files/plugin/lib/hobo/controller.rb +35 -12
  37. data/hobo_files/plugin/lib/hobo/define_tags.rb +4 -4
  38. data/hobo_files/plugin/lib/hobo/dryml.rb +33 -51
  39. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +47 -34
  40. data/hobo_files/plugin/lib/hobo/dryml/scoped_variables.rb +37 -0
  41. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +27 -5
  42. data/hobo_files/plugin/lib/hobo/dryml/template.rb +545 -302
  43. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +305 -135
  44. data/hobo_files/plugin/lib/hobo/email_address.rb +5 -0
  45. data/hobo_files/plugin/lib/hobo/field_spec.rb +66 -0
  46. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +325 -0
  47. data/hobo_files/plugin/lib/hobo/html_string.rb +2 -0
  48. data/hobo_files/plugin/lib/hobo/lazy_hash.rb +13 -1
  49. data/hobo_files/plugin/lib/hobo/markdown_string.rb +3 -1
  50. data/hobo_files/plugin/lib/hobo/model.rb +185 -66
  51. data/hobo_files/plugin/lib/hobo/model_controller.rb +56 -49
  52. data/hobo_files/plugin/lib/hobo/password_string.rb +2 -0
  53. data/hobo_files/plugin/lib/hobo/plugins.rb +75 -0
  54. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +98 -0
  55. data/hobo_files/plugin/lib/hobo/static_tags +0 -3
  56. data/hobo_files/plugin/lib/hobo/textile_string.rb +11 -1
  57. data/hobo_files/plugin/lib/hobo/undefined.rb +1 -1
  58. data/hobo_files/plugin/lib/rexml.rb +166 -75
  59. data/hobo_files/plugin/spec/fixtures/users.yml +9 -0
  60. data/hobo_files/plugin/spec/spec.opts +6 -0
  61. data/hobo_files/plugin/spec/spec_helper.rb +28 -0
  62. data/hobo_files/plugin/spec/unit/hobo/dryml/template_spec.rb +650 -0
  63. data/hobo_files/plugin/tags/core.dryml +58 -4
  64. data/hobo_files/plugin/tags/rapid.dryml +289 -135
  65. data/hobo_files/plugin/tags/rapid_document_tags.dryml +49 -0
  66. data/hobo_files/plugin/tags/rapid_editing.dryml +92 -69
  67. data/hobo_files/plugin/tags/rapid_forms.dryml +242 -0
  68. data/hobo_files/plugin/tags/rapid_navigation.dryml +65 -65
  69. data/hobo_files/plugin/tags/rapid_pages.dryml +197 -124
  70. data/hobo_files/plugin/tags/rapid_support.dryml +23 -0
  71. metadata +29 -22
  72. data/hobo_files/plugin/generators/hobo_model/templates/migration.rb +0 -13
  73. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/default_mapping.rb +0 -11
  74. data/hobo_files/plugin/generators/hobo_user_model/templates/migration.rb +0 -15
  75. data/hobo_files/plugin/lib/hobo/HtmlString +0 -3
  76. data/hobo_files/plugin/lib/hobo/controller_helpers.rb +0 -135
  77. data/hobo_files/plugin/lib/hobo/core.rb +0 -475
  78. data/hobo_files/plugin/lib/hobo/rapid.rb +0 -447
  79. data/hobo_files/plugin/test/hobo_dryml_template_test.rb +0 -7
  80. data/hobo_files/plugin/test/hobo_test.rb +0 -7
@@ -1,4 +1,9 @@
1
- Hobo
2
- ====
1
+ == Welcome to Hobo
2
+ Hobo is an Open Source extension to Ruby on Rails which helps you build full blown web applications incredibly quickly and easily. Available as a Gem or Rails plugin, Hobo provides a simple, clean and elegant development framework which allows for rapid prototyping or production of the most sophisticated web applications.
3
+
4
+ == Main Features
5
+
6
+ * Rapid implementation of dynamic Ajax interfaces in your application with no extra programming.
7
+ * Switchable themes. Customise and tweak your application structure and layout to meet any design goals.
8
+ * Powerful mark-up language, DRYML, combines rapid development with ultimate design flexibility. The end of the cookie cutter blues!
3
9
 
4
- Description goes here
@@ -1,5 +1,9 @@
1
1
  require 'rake'
2
2
  require 'rake/rdoctask'
3
+ require 'rake/testtask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :spec
3
7
 
4
8
  desc 'Generate documentation for the Hobo plugin.'
5
9
  Rake::RDocTask.new(:rdoc) do |rdoc|
@@ -9,3 +13,80 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
9
13
  rdoc.rdoc_files.include('README')
10
14
  rdoc.rdoc_files.include('lib/**/*.rb')
11
15
  end
16
+
17
+
18
+
19
+ # --- RSpec --- #
20
+
21
+ # In rails 1.2, plugins aren't available in the path until they're loaded.
22
+ # Check to see if the rspec plugin is installed first and require
23
+ # it if it is. If not, use the gem version.
24
+ PLUGIN_DIR = File.dirname(__FILE__)
25
+
26
+ rspec_base = File.expand_path(PLUGIN_DIR + '/spec/rails_root/vendor/plugins/rspec/lib')
27
+ $LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
28
+ require 'spec/rake/spectask'
29
+ require 'spec/translator'
30
+
31
+ spec_prereq = :noop # File.exist?(File.join(PLUGIN_DIR, 'config', 'database.yml')) ? "db:test:prepare" : :noop
32
+ task :noop do
33
+ end
34
+
35
+ task :stats => "spec:statsetup"
36
+
37
+ desc "Run all specs in spec directory (excluding plugin specs)"
38
+ Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t|
39
+ t.spec_opts = ['--options', "\"#{PLUGIN_DIR}/spec/spec.opts\""]
40
+ t.spec_files = FileList['spec/unit/**/*_spec.rb']
41
+ end
42
+
43
+ namespace :spec do
44
+ desc "Run all specs in spec directory with RCov (excluding plugin specs)"
45
+ Spec::Rake::SpecTask.new(:rcov) do |t|
46
+ t.spec_opts = ['--options', "\"#{PLUGIN_DIR}/spec/spec.opts\""]
47
+ t.spec_files = FileList['spec/unit/**/*_spec.rb']
48
+ t.rcov = true
49
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
50
+ end
51
+
52
+ desc "Print Specdoc for all specs (excluding plugin specs)"
53
+ Spec::Rake::SpecTask.new(:doc) do |t|
54
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
55
+ t.spec_files = FileList['spec/unit/**/*_spec.rb']
56
+ end
57
+
58
+ [:models, :controllers, :views, :helpers].each do |sub|
59
+ desc "Run the specs under spec/#{sub}"
60
+ Spec::Rake::SpecTask.new(sub => spec_prereq) do |t|
61
+ t.spec_opts = ['--options', "\"#{PLUGIN_DIR}/spec/spec.opts\""]
62
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
63
+ end
64
+ end
65
+
66
+ # Setup specs for stats
67
+ task :statsetup do
68
+ require 'code_statistics'
69
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
70
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
71
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
72
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
73
+ ::CodeStatistics::TEST_TYPES << "Model specs"
74
+ ::CodeStatistics::TEST_TYPES << "View specs"
75
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
76
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
77
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
78
+ end
79
+
80
+ namespace :db do
81
+ namespace :fixtures do
82
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
83
+ task :load => :environment do
84
+ require 'active_record/fixtures'
85
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
86
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(PLUGIN_DIR, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
87
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -15,17 +15,17 @@ class HoboGenerator < Rails::Generator::Base
15
15
  end
16
16
 
17
17
  record do |m|
18
- m.directory File.join("app/views/hobolib")
19
- m.directory File.join("app/views/hobolib/themes")
18
+ m.directory File.join("app/views/taglibs")
19
+ m.directory File.join("app/views/taglibs/themes")
20
20
  m.directory File.join("public/hobothemes")
21
- m.file "application.dryml", File.join("app/views/hobolib/application.dryml")
21
+ m.file "application.dryml", File.join("app/views/taglibs/application.dryml")
22
22
  m.file "guest.rb", File.join("app/models/guest.rb")
23
23
  end
24
24
  end
25
25
 
26
26
  protected
27
27
  def banner
28
- "Usage: #{$0} generate [--add-routes]"
28
+ "Usage: #{$0} #{spec.name} [--add-routes]"
29
29
  end
30
30
 
31
31
  def add_options!(opt)
@@ -1,6 +1,6 @@
1
1
  class Guest
2
2
 
3
- def display_name
3
+ def to_s
4
4
  "Guest"
5
5
  end
6
6
 
@@ -73,7 +73,7 @@ class HoboFrontControllerGenerator < Rails::Generator::NamedBase
73
73
 
74
74
  protected
75
75
  def banner
76
- "Usage: #{$0} generate <controller-name> [--add-routes] [--no-user] [--delete-index]"
76
+ "Usage: #{$0} #{spec.name} <controller-name> [--add-routes] [--no-user] [--delete-index]"
77
77
  end
78
78
 
79
79
  def add_options!(opt)
@@ -1,43 +1,37 @@
1
- <page title="<%= app_name %>">
1
+ <Page title="<%= app_name %>">
2
2
 
3
- <:intro>
4
- <h1 class="front_page_title"><%= app_name %></h1>
5
- </:intro>
6
-
7
- <:main>
3
+ <main>
4
+ <header>
5
+ <h1 class="front_page_title"><%= app_name %></h1>
6
+ </header>
8
7
  <panel>
9
8
  <div style="margin-left: 40px; ">
10
9
  <h3>Congratulations! Your Hobo Rails App is up and running</h3>
11
10
  <ul>
12
11
  <li>To customise this page: edit app/views/<%= file_name %>/index.dryml </li>
13
-
14
- <li>To change the nav-bar: define &lt;application_nav&gt;<br/>
15
- See vendor/plugins/hobo/tags/rapid.dryml for an example</li>
16
-
17
- <li>To change the logo: define &lt;application_logo&gt;.</li>
18
12
  </ul>
19
13
  </div>
20
14
  </panel>
21
15
 
22
- <repeat obj="#Hobo.models">
16
+ <repeat with="&Hobo.models">
23
17
  <panel>
24
- <h2><%%= this.name.titlecase.pluralize %></h2>
18
+ <header><h2><%%= this.name.titlecase.pluralize %></h2></header>
25
19
  <section>
26
- <if q="#this.count == 0">
20
+ <if test="&this.count == 0">
27
21
  <p>There are no <%%= this.name.titleize.pluralize %></p>
28
- <if q="#can_create?(this) and this != Hobo.user_model">
29
- <p>Create a <new_object_link for="#this"/>.</p>
22
+ <if test="&can_create?(this) and this != Hobo.user_model">
23
+ <p>Create a <a to="&this.new"/>.</p>
30
24
  </if>
31
25
  </if>
32
26
  <else>
33
- <repeat obj="#this.find(:all, :limit => 3)">
34
- <if_can_view><object_card/></if_can_view>
27
+ <repeat with="&this.find(:all, :limit => 3)">
28
+ <card if="&can_view?"/>
35
29
  </repeat>
36
- <p><object_link>More</object_link> (<count/>)</p>
30
+ <p><a>More</a> (<count/>)</p>
37
31
  </else>
38
32
  </section>
39
33
  </panel>
40
34
  </repeat>
41
- </:main>
42
-
43
- </page>
35
+ </main>
36
+
37
+ </Page>
@@ -1,10 +1,8 @@
1
- <page title="<%= app_name %>">
1
+ <Page title="<%= app_name %>">
2
2
 
3
- <:intro>
3
+ <main>
4
4
  <h1 class="front_page_title"><%= app_name %></h1>
5
- </:intro>
6
5
 
7
- <:main>
8
6
  <panel>
9
7
  <h2>Login to <%= app_name %></h2>
10
8
  <section>
@@ -39,6 +37,6 @@
39
37
  Edit app/views/<%= full_class_path %>/login.dryml to customise this page
40
38
  </div>
41
39
  </panel>
42
- </:main>
40
+ </main>
43
41
 
44
- </page>
42
+ </Page>
@@ -1,6 +1,7 @@
1
- <page title="Search" onload="Hobo.doSearch('search_field')">
1
+ <Page title="Search">
2
+ <body onload="Hobo.applyEvents(); Hobo.doSearch('search_field')"/>
2
3
 
3
- <:main>
4
+ <main>
4
5
  <panel class="red" style="margin-top: 40px">
5
6
  <h2>Search</h2>
6
7
  <div class='search' style='margin:10px'>
@@ -11,8 +12,8 @@
11
12
 
12
13
  <panel class="hidden" id="search_results_panel">
13
14
  <h2>Results</h2>
14
- <section id="search_results"></section>
15
+ <section id="search_results">&nbsp;</section>
15
16
  </panel>
16
- </:main>
17
+ </main>
17
18
 
18
- </page>
19
+ </Page>
@@ -1,10 +1,8 @@
1
- <page title="<%= app_name %>">
1
+ <Page title="<%= app_name %>">
2
2
 
3
- <:intro>
3
+ <main>
4
4
  <h1 class="front_page_title"><%= app_name %></h1>
5
- </:intro>
6
5
 
7
- <:main>
8
6
  <panel>
9
7
  <h2>Sign Up</h2>
10
8
  <section>
@@ -39,7 +37,7 @@
39
37
  Edit app/views/<%= full_class_path %>/signup.dryml to customise this page
40
38
  </div>
41
39
  </panel>
42
- </:main>
40
+ </main>
43
41
 
44
- </page>
42
+ </Page>
45
43
 
@@ -0,0 +1,237 @@
1
+ class HoboMigrationGenerator < Rails::Generator::Base
2
+
3
+ def initialize(runtime_args, runtime_options = {})
4
+ super
5
+ @migration_name = runtime_args.first || begin
6
+ i = Dir["#{RAILS_ROOT}/db/migrate/*hobo_migration*"].length
7
+ "hobo_migration_#{i+1}"
8
+ end
9
+ end
10
+
11
+ def manifest
12
+ connection = ActiveRecord::Base.connection
13
+ @types = connection.native_database_types
14
+
15
+ # Force load of hobo models
16
+ Hobo.models
17
+
18
+ models = ActiveRecord::Base.send(:subclasses).reject {|c| c.name.starts_with?("CGI::") }
19
+ table_models = models.index_by {|m| m.table_name}
20
+ model_table_names = models.every(:table_name)
21
+
22
+ to_create = model_table_names - connection.tables
23
+ to_drop = connection.tables - model_table_names - ['schema_info']
24
+ to_change = connection.tables & model_table_names
25
+
26
+ to_rename = rename_or_drop!(to_create, to_drop, "table")
27
+
28
+ renames = to_rename.map do |old_name, new_name|
29
+ "rename_table :#{old_name}, :#{new_name}"
30
+ end * "\n"
31
+ undo_renames = to_rename.map do |old_name, new_name|
32
+ "rename_table :#{new_name}, :#{old_name}"
33
+ end * "\n"
34
+
35
+ drops = to_drop.map do |t|
36
+ "drop_table :#{t}"
37
+ end * "\n"
38
+ undo_drops = to_drop.map do |t|
39
+ revert_table(t)
40
+ end * "\n\n"
41
+
42
+ creates = to_create.map do |t|
43
+ create_table(table_models[t])
44
+ end * "\n\n"
45
+ undo_creates = to_create.map do |t|
46
+ "drop_table :#{t}"
47
+ end * "\n"
48
+
49
+ changes = []
50
+ undo_changes = []
51
+ to_change.each do |t|
52
+ change, undo = change_table(table_models[t])
53
+ changes << change
54
+ undo_changes << undo
55
+ end
56
+
57
+ up = [renames, drops, creates, changes * "\n\n"].select{|s|!s.blank?} * "\n\n"
58
+ down = [undo_renames, undo_drops, undo_creates, undo_changes * "\n\n"].select{|s|!s.blank?} * "\n\n"
59
+
60
+ puts "\n---------- Up Migration ----------", up, "----------------------------------"
61
+ puts "\n---------- Down Migration --------", down, "----------------------------------"
62
+
63
+ return record {|m| } if up.blank?
64
+
65
+ up.gsub!("\n", "\n ")
66
+ down.gsub!("\n", "\n ")
67
+
68
+ action = input("What now: [g]enerate migrations, generate and [m]igrate now or [c]ancel?", %w(g m c))
69
+
70
+ if action == 'c'
71
+ # record nothing to keep the generator happy
72
+ record {|m| }
73
+ else
74
+ at_exit { system "rake db:migrate" } if action == 'm'
75
+
76
+ up.gsub!("\n", "\n ")
77
+ down.gsub!("\n", "\n ")
78
+
79
+ record do |m|
80
+ m.migration_template 'migration.rb', 'db/migrate',
81
+ :assigns => { :up => up, :down => down, :migration_name => @migration_name.camelize },
82
+ :migration_file_name => @migration_name
83
+ end
84
+ end
85
+ end
86
+
87
+ def rename_or_drop!(to_create, to_drop, kind_str, name_prefix="")
88
+ to_rename = {}
89
+ rename_to_choices = to_create
90
+ to_drop.dup.each do |t|
91
+ if rename_to_choices.empty?
92
+ puts "\nCONFIRM DROP! #{kind_str} #{name_prefix}#{t}"
93
+ resp = input("Enter 'drop #{t}' to confirm:")
94
+ if resp.strip != "drop " + t.to_s
95
+ to_drop.delete(t)
96
+ end
97
+ else
98
+ puts "\nDROP or RENAME?: #{kind_str} #{name_prefix}#{t}"
99
+ puts "Rename choices: #{to_create * ', '}"
100
+ resp = input("Enter either 'drop #{t}' or one of the rename choices:")
101
+ resp.strip!
102
+
103
+ if resp == "drop " + t
104
+ # Leave things as they are
105
+ else
106
+ to_drop.delete(t)
107
+ if resp.in?(rename_to_choices)
108
+ to_rename[t] = resp
109
+ to_create.delete(resp)
110
+ rename_to_choices.delete(resp)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ to_rename
116
+ end
117
+
118
+ def create_table(model)
119
+ longest_field_name = model.field_specs.values.map { |f| f.sql_type.to_s.length }.max
120
+ (["create_table :#{model.table_name} do |t|"] +
121
+ model.field_specs.values.sort_by{|f| f.position}.map {|f| create_field(f, longest_field_name)} +
122
+ ["end"]) * "\n"
123
+ end
124
+
125
+ def create_field(field_spec, field_name_width)
126
+ args = [field_spec.name.inspect] + format_options(field_spec.options, field_spec.sql_type)
127
+ " t.%-*s %s" % [field_name_width, field_spec.sql_type, args.join(', ')]
128
+ end
129
+
130
+ def change_table(model)
131
+ table_name = model.table_name
132
+ db_columns = model.connection.columns(model.table_name).index_by{|c|c.name} - [model.primary_key]
133
+ model_column_names = model.field_specs.keys.every(:to_s)
134
+ db_column_names = db_columns.keys.every(:to_s)
135
+
136
+ to_add = model_column_names - db_column_names
137
+ to_remove = db_column_names - model_column_names - [model.primary_key.to_sym]
138
+
139
+ to_rename = rename_or_drop!(to_add, to_remove, "column", "#{table_name}.")
140
+
141
+ db_column_names -= to_rename.keys
142
+ db_column_names |= to_rename.values
143
+ to_change = db_column_names & model_column_names
144
+
145
+ renames = to_rename.map do |old_name, new_name|
146
+ "rename_column :#{table_name}, :#{old_name}, :#{new_name}"
147
+ end
148
+ undo_renames = to_rename.map do |old_name, new_name|
149
+ "rename_column :#{table_name}, :#{new_name}, :#{old_name}"
150
+ end
151
+
152
+ to_add = to_add.sort_by{|c| model.field_specs[c].position }
153
+ adds = to_add.map do |c|
154
+ spec = model.field_specs[c]
155
+ args = [":#{spec.sql_type}"] + format_options(spec.options, spec.sql_type)
156
+ "add_column :#{table_name}, :#{c}, #{args * ', '}"
157
+ end
158
+ undo_adds = to_add.map do |c|
159
+ "remove_column :#{table_name}, :#{c}"
160
+ end
161
+
162
+ removes = to_remove.map do |c|
163
+ "remove_column :#{table_name}, :#{c}"
164
+ end
165
+ undo_removes = to_remove.map do |c|
166
+ revert_column(table_name, c)
167
+ end
168
+
169
+ old_names = to_rename.invert
170
+ changes = []
171
+ undo_changes = []
172
+ to_change.each do |c|
173
+ col_name = old_names[c] || c
174
+ col = db_columns[col_name]
175
+ spec = model.field_specs[c]
176
+ if spec.different_to?(col)
177
+ change_spec = {}
178
+ change_spec[:limit] = spec.limit if !spec.limit.nil?
179
+ change_spec[:precision] = spec.precision if !spec.precision.nil?
180
+ change_spec[:scale] = spec.scale if !spec.scale.nil?
181
+ change_spec[:null] = false unless spec.null
182
+ change_spec[:default] = spec.default if !spec.default.nil?
183
+
184
+ changes << "change_column :#{table_name}, :#{c}, " +
185
+ ([":#{spec.sql_type}"] + format_options(change_spec, spec.sql_type)).join(", ")
186
+ back = change_column_back(table_name, c)
187
+ undo_changes << back unless back.blank?
188
+ else
189
+ nil
190
+ end
191
+ end.compact
192
+
193
+ [(renames + adds + removes + changes) * "\n",
194
+ (undo_renames + undo_adds + undo_removes + undo_changes) * "\n"]
195
+ end
196
+
197
+
198
+ def format_options(options, type)
199
+ options.map do |k, v|
200
+ next if k == :limit && (type == :decimal || v == @types[type][:limit])
201
+ next if k == :null && v == true
202
+ "#{k.inspect} => #{v.inspect}"
203
+ end.compact
204
+ end
205
+
206
+
207
+ def revert_table(table)
208
+ res = StringIO.new
209
+ ActiveRecord::SchemaDumper.send(:new, ActiveRecord::Base.connection).send(:table, table, res)
210
+ res.string.strip.gsub("\n ", "\n")
211
+ end
212
+
213
+
214
+ def change_column_back(table, column)
215
+ _, type, options = *revert_table(table).match(/\s*t\.column\s+"#{column}",\s+(:[a-zA-Z0-9_]+)(?:,\s+(.*?)$)?/m)
216
+ "change_column :#{table}, :#{column}, #{type}#{', ' + options.strip if options}"
217
+ end
218
+
219
+ def revert_column(table, column)
220
+ "add_column :#{table}, :#{column}, " + revert_table(table).match(/\s*t\.column\s+"#{column}",\s+(.*?)$/m)[1].strip
221
+ end
222
+
223
+
224
+ def input(prompt, options=nil)
225
+ print(prompt + " ")
226
+ if options
227
+ while !(response = STDIN.readline.strip.downcase).in?(options);
228
+ print(prompt + " ")
229
+ end
230
+ response
231
+ else
232
+ STDIN.readline
233
+ end
234
+ end
235
+
236
+ end
237
+