hobo 0.5.3 → 0.6

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