fortitude 0.9.5 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +11 -0
  3. data/CONTRIBUTORS.md +4 -0
  4. data/fortitude.gemspec +1 -1
  5. data/lib/fortitude/rails/helpers.rb +20 -1
  6. data/lib/fortitude/rails/railtie.rb +2 -0
  7. data/lib/fortitude/rails/yielded_object_outputter.rb +3 -0
  8. data/lib/fortitude/version.rb +1 -1
  9. data/lib/fortitude/widget/integration.rb +15 -1
  10. data/lib/rails/generators/fortitude/base_view/base_view_generator.rb +13 -0
  11. data/lib/rails/generators/fortitude/base_view/templates/views_base.rb +230 -0
  12. data/lib/rails/generators/fortitude/controller/controller_generator.rb +18 -0
  13. data/lib/rails/generators/fortitude/controller/templates/view.html.rb +6 -0
  14. data/lib/rails/generators/fortitude/controller/templates/views_base.rb +230 -0
  15. data/lib/rails/generators/fortitude/mailer/mailer_generator.rb +26 -0
  16. data/lib/rails/generators/fortitude/mailer/templates/layout.html.rb +9 -0
  17. data/lib/rails/generators/fortitude/mailer/templates/view.html.rb +12 -0
  18. data/lib/rails/generators/fortitude/scaffold/scaffold_generator.rb +29 -0
  19. data/lib/rails/generators/fortitude/scaffold/templates/edit.html.rb +13 -0
  20. data/lib/rails/generators/fortitude/scaffold/templates/form.html.rb +44 -0
  21. data/lib/rails/generators/fortitude/scaffold/templates/index.html.rb +45 -0
  22. data/lib/rails/generators/fortitude/scaffold/templates/new.html.rb +11 -0
  23. data/lib/rails/generators/fortitude/scaffold/templates/show.html.rb +18 -0
  24. data/spec/rails/complex_helpers_system_spec.rb +6 -0
  25. data/spec/rails/data_passing_system_spec.rb +4 -0
  26. data/spec/rails/generators_system_spec.rb +166 -0
  27. data/spec/rails/templates/complex_helpers_system_spec/app/controllers/carryover_controller.rb +11 -0
  28. data/spec/rails/templates/complex_helpers_system_spec/app/views/carryover/show.html.rb +9 -0
  29. data/spec/rails/templates/complex_helpers_system_spec/app/views/complex_helpers_system_spec/form_for_test.rb +2 -0
  30. data/spec/rails/templates/complex_helpers_system_spec/config/routes.rb +6 -0
  31. data/spec/rails/templates/data_passing_system_spec/app/controllers/data_passing_system_spec_controller.rb +5 -0
  32. data/spec/rails/templates/data_passing_system_spec/app/views/data_passing_system_spec/nil_data_widget.rb +8 -0
  33. data/spec/rails/templates/generators_system_spec/config/environments/development.rb +39 -0
  34. metadata +30 -4
@@ -0,0 +1,26 @@
1
+ require 'rails/generators/erb/mailer/mailer_generator'
2
+
3
+ module Fortitude
4
+ module Generators
5
+ class MailerGenerator < ::Erb::Generators::MailerGenerator
6
+ source_root File.expand_path("../templates", __FILE__)
7
+
8
+ def create_view_base
9
+ generate "fortitude:base_view"
10
+ end
11
+
12
+ protected
13
+ def handler
14
+ :rb
15
+ end
16
+
17
+ def formats
18
+ [:html]
19
+ end
20
+
21
+ def format
22
+ :html
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ class Views::Layouts::Mailer < Views::Base
2
+ def contents
3
+ html {
4
+ body {
5
+ yield
6
+ }
7
+ }
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ <%
2
+ our_class_name = class_name.camelize
3
+ our_class_name += "Mailer" if @path =~ %r{_mailer/}
4
+ -%>
5
+ class Views::<%= our_class_name %>::<%= @action.camelize %> < Views::Base
6
+ needs :greeting
7
+
8
+ def content
9
+ h1 "<%= class_name %>#<%= @action %>"
10
+ p "#{greeting}, find me in <%= @path %>"
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ require 'rails/generators/erb/scaffold/scaffold_generator'
2
+
3
+ module Fortitude
4
+ module Generators
5
+ class ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
6
+ source_root File.join(File.dirname(__FILE__), 'templates')
7
+
8
+ def copy_view_files
9
+ available_views.each do |view|
10
+ filename = filename_with_extensions(view)
11
+ template "#{view}.html.rb", File.join("app/views", controller_file_path, filename)
12
+ end
13
+ end
14
+
15
+ def create_view_base
16
+ generate "fortitude:base_view"
17
+ end
18
+
19
+ protected
20
+ def available_views
21
+ %w(index new show edit form)
22
+ end
23
+
24
+ def handler
25
+ :rb
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ class Views::<%= plural_table_name.camelize %>::Edit < Views::Base
2
+ needs :<%= singular_table_name %>
3
+
4
+ def content
5
+ h1 "Editing <%= singular_table_name.titleize %>"
6
+
7
+ widget Views::<%= plural_table_name.camelize %>::Form, :<%= singular_table_name %> => <%= singular_table_name %>
8
+
9
+ link_to 'Show', <%= singular_table_name %>
10
+ text " | "
11
+ link_to 'Back', <%= index_helper %>_path
12
+ end
13
+ end
@@ -0,0 +1,44 @@
1
+ class Views::<%= plural_table_name.camelize %>::Form < Views::Base
2
+ needs :<%= singular_table_name %>
3
+
4
+ def content
5
+ form_for(<%= singular_table_name %>) do |f|
6
+ if <%= singular_table_name %>.errors.any?
7
+ div(:id => :error_explanation) {
8
+ h2 {
9
+ text pluralize(<%= singular_table_name %>.errors.count, "error")
10
+ text " prohibited this <%= singular_table_name %> from being saved:"
11
+ }
12
+
13
+ ul {
14
+ <%= singular_table_name %>.errors.full_messages.each do |message|
15
+ li message
16
+ end
17
+ }
18
+ }
19
+ end
20
+
21
+ <% attributes.each do |attribute| -%>
22
+ <% if attribute.respond_to?(:password_digest?) && attribute.password_digest? -%>
23
+ div(:class => :field) {
24
+ f.label :password
25
+ f.password_field :password
26
+ }
27
+
28
+ div(:class => :field) {
29
+ f.label :password_confirmation
30
+ f.password_field :password_confirmation
31
+ }
32
+ <% else -%>
33
+ <% name = if attribute.respond_to?(:column_name) then attribute.column_name else attribute.name end -%>
34
+ div(:class => :field) {
35
+ f.label :<%= name %>
36
+ f.<%= attribute.field_type %> :<%= name %>
37
+ }
38
+ <% end -%>
39
+ <% end -%>
40
+
41
+ div(:class => :actions) { f.submit }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,45 @@
1
+ class Views::<%= plural_table_name.camelize %>::Index < Views::Base
2
+ needs :<%= plural_table_name %>, :notice => nil
3
+
4
+ def content
5
+ p notice, :id => :notice
6
+
7
+ h1 "<%= plural_table_name.titleize %>"
8
+
9
+ table {
10
+ thead {
11
+ tr {
12
+ <% attributes.reject { |a| a.respond_to?(:password_digest?) && a.password_digest? }.each do |attribute| -%>
13
+ th "<%= attribute.human_name %>"
14
+ <% end -%>
15
+ th
16
+ th
17
+ th
18
+ }
19
+ }
20
+
21
+ tbody {
22
+ <%= plural_table_name %>.each do |<%= singular_table_name %>|
23
+ tr {
24
+ <% attributes.reject { |a| a.respond_to?(:password_digest?) && a.password_digest? }.each do |attribute| -%>
25
+ td <%= singular_table_name %>.<%= attribute.name %>
26
+ <% end -%>
27
+ td {
28
+ link_to 'Show', <%= singular_table_name %>
29
+ }
30
+ td {
31
+ link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>)
32
+ }
33
+ td {
34
+ link_to 'Destroy', <%= singular_table_name %>, :method => :delete, :data => { :confirm => 'Are you sure?' }
35
+ }
36
+ }
37
+ end
38
+ }
39
+ }
40
+
41
+ br
42
+
43
+ link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_table_name %>_path
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ class Views::<%= plural_table_name.camelize %>::New < Views::Base
2
+ needs :<%= singular_table_name %> => nil
3
+
4
+ def content
5
+ h1 "New <%= singular_table_name.titleize %>"
6
+
7
+ widget Views::<%= plural_table_name.camelize %>::Form, :<%= singular_table_name %> => <%= singular_table_name %>
8
+
9
+ link_to 'Back', <%= index_helper %>_path
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ class Views::<%= plural_table_name.camelize %>::Show < Views::Base
2
+ needs :<%= singular_table_name %>, :notice => nil
3
+
4
+ def content
5
+ p notice, :id => :notice
6
+
7
+ <% attributes.reject { |a| a.respond_to?(:password_digest?) && a.password_digest? }.each do |attribute| -%>
8
+ p {
9
+ strong "<%= attribute.human_name %>:"
10
+ text <%= singular_table_name %>.<%= attribute.name %>
11
+ }
12
+ <% end %>
13
+
14
+ link_to "Edit", edit_<%= singular_table_name %>_path(<%= singular_table_name %>)
15
+ text " | "
16
+ link_to 'Back', <%= index_helper %>_path
17
+ end
18
+ end
@@ -6,6 +6,7 @@ describe "Rails complex helper support", :type => :rails do
6
6
  %r{OUTSIDE_BEFORE\s*<form.*action=\"/complex_helpers_system_spec/form_for_test\".*
7
7
  INSIDE_BEFORE\s*
8
8
  FIRST:\s*<input.*person_first_name.*/>\s*
9
+ MIDDLE:\s*<input.*person_middle_name.*/>\s*
9
10
  LAST:\s*<input.*person_last_name.*/>\s*
10
11
  INSIDE_AFTER\s*
11
12
  </form>\s*
@@ -67,6 +68,11 @@ describe "Rails complex helper support", :type => :rails do
67
68
  </label>}mix)
68
69
  end
69
70
 
71
+ it "should allow implicitly carrying through things like IDs from one request to another" do
72
+ id = rand(1_000_000)
73
+ expect(rails_server.get("/carryover/#{id}")).to match(%r{Edit:\s*.*/carryover/#{id}/edit})
74
+ end
75
+
70
76
  it "should cache based on a name properly" do
71
77
  expect_match("cache_test?a=a1&b=b1",
72
78
  /before_cache\(a1,b1\).*inside_cache\(a1,b1\).*after_cache\(a1,b1\)/mi)
@@ -21,6 +21,10 @@ describe "Rails data-passing support", :type => :rails do
21
21
  expect_actionview_exception('omitted_variable', 'Fortitude::Errors::MissingNeed', /bar/)
22
22
  end
23
23
 
24
+ it "should not give you an error just because a variable is set to nil" do
25
+ expect_match("nil_data_widget", /foo is: &quot;the_foo&quot;/, /bar is: nil/)
26
+ end
27
+
24
28
  it "should not propagate un-needed variables" do
25
29
  expect_match("extra_variables", /foo method call: the_foo/, /foo instance var: nil/,
26
30
  /bar method call: NoMethodError/, /bar instance var: nil/,
@@ -0,0 +1,166 @@
1
+ describe "Rails generator support", :type => :rails do
2
+ # We use development mode so that we don't have to bounce the Rails server every time we
3
+ # generate something new.
4
+ uses_rails_with_template :generators_system_spec, :rails_env => :development
5
+
6
+ def views_base_path
7
+ @views_base_path ||= File.join(rails_server.rails_root, 'app', 'views', 'base.rb')
8
+ end
9
+
10
+ def clean_views_base!
11
+ File.delete(views_base_path) if File.exist?(views_base_path)
12
+ end
13
+
14
+ def ensure_views_base_is_correct!
15
+ expect(File.exist?(views_base_path)).to be_truthy
16
+
17
+ contents = File.read(views_base_path)
18
+ expect(contents).to match(/Views::Base\s*<\s*Fortitude::Widget\s*\n/)
19
+ expect(contents).to match(/doctype\s+:html5/)
20
+ end
21
+
22
+ def generate!(what)
23
+ rails_server.run_command_in_rails_root!("rails generate #{what}")
24
+ end
25
+
26
+ def ensure_file_matches!(subpath, regexp)
27
+ path = File.join(rails_server.rails_root, subpath)
28
+ expect(File.exist?(path)).to be_truthy
29
+
30
+ contents = File.read(path)
31
+ expect(contents).to match(regexp)
32
+ end
33
+
34
+ def ensure_action_matches!(subpath, regexp)
35
+ response = rails_server.get(subpath)
36
+ expect(response).to match(regexp)
37
+ response
38
+ end
39
+
40
+ describe "base view generation" do
41
+ it "should be able to generate a Views::Base file" do
42
+ clean_views_base!
43
+ generate!("fortitude:base_view")
44
+ ensure_views_base_is_correct!
45
+ end
46
+ end
47
+
48
+ describe "controller generation" do
49
+ it "should be able to generate a controller action that creates a Fortitude view" do
50
+ generate!("controller gen_con1 act_ion1")
51
+ ensure_file_matches!('app/views/gen_con1/act_ion1.html.rb', /Views::GenCon1::ActIon1\s*<\s*Views::Base/)
52
+ ensure_action_matches!('gen_con1/act_ion1', %r{<h1>\s*GenCon1#act_ion1\s*</h1>\s*<p>\s*Find me in app/views/gen_con1/act_ion1.html.rb\s*</p>}mi)
53
+ end
54
+
55
+ it "should generate a Views::Base file" do
56
+ clean_views_base!
57
+ generate!("controller gencon2 action2")
58
+ ensure_views_base_is_correct!
59
+ end
60
+
61
+ it "should not overwrite an existing Views::Base file" do
62
+ prior_contents = "class Views::Base < Fortitude::Widget\n doctype :html5\n def something\n end\nend"
63
+ File.open(views_base_path, 'w') { |f| f.puts prior_contents }
64
+
65
+ generate!("controller gencon3 action3")
66
+
67
+ expect(File.exist?(views_base_path)).to be_truthy
68
+ expect(File.read(views_base_path).strip).to eq(prior_contents.strip)
69
+ end
70
+
71
+ it "should allow switching back to ERb if desired" do
72
+ generate!("controller gen_con4 act_ion1 -e erb")
73
+ expect(File.exist?(File.join(rails_server.rails_root, 'app/views/gen_con4/act_ion1.html.rb'))).to be_falsey
74
+ expect(File.exist?(File.join(rails_server.rails_root, 'app/views/gen_con4/act_ion1.html.erb'))).to be_truthy
75
+ end
76
+ end
77
+
78
+ describe "mailer generation" do
79
+ def mailer_path_suffix
80
+ if rails_server.actual_rails_version =~ /^[34]\./
81
+ ""
82
+ else
83
+ "_mailer"
84
+ end
85
+ end
86
+
87
+ def mailer_class_suffix
88
+ if rails_server.actual_rails_version =~ /^[34]\./
89
+ ""
90
+ else
91
+ "Mailer"
92
+ end
93
+ end
94
+
95
+ it "should be able to generate a mailer that creates a Fortitude view and layout" do
96
+ generate!("mailer gen1 act_ion1")
97
+ ensure_file_matches!("app/views/gen1#{mailer_path_suffix}/act_ion1.html.rb", /Views::Gen1#{mailer_class_suffix}::ActIon1\s*<\s*Views::Base/)
98
+ end
99
+
100
+ it "should generate a Views::Base file" do
101
+ clean_views_base!
102
+ generate!("mailer gen1 act_ion1")
103
+ ensure_views_base_is_correct!
104
+ end
105
+
106
+ it "should not overwrite an existing Views::Base file" do
107
+ prior_contents = "class Views::Base < Fortitude::Widget\n doctype :html5\n def something\n end\nend"
108
+ File.open(views_base_path, 'w') { |f| f.puts prior_contents }
109
+
110
+ generate!("mailer gen1 act_ion1")
111
+
112
+ expect(File.exist?(views_base_path)).to be_truthy
113
+ expect(File.read(views_base_path).strip).to eq(prior_contents.strip)
114
+ end
115
+ end
116
+
117
+ describe "scaffold generation" do
118
+ it "should be able to generate a scaffold" do
119
+ generate!("scaffold MyModel foo:string bar:integer")
120
+
121
+ # We need to disable CSRF protection, since we're not using a session
122
+ application_controller = File.join(rails_server.rails_root, 'app', 'controllers', 'application_controller.rb')
123
+ application_controller_contents = File.read(application_controller)
124
+ if (! application_controller_contents.gsub!(/^\s*protect_from_forgery.*$/, "protect_from_forgery :with => :null_session"))
125
+ application_controller_contents.gsub!(/^end\Z/, " protect_from_forgery :with => :null_session\nend\n")
126
+ end
127
+ File.open(application_controller, 'w') { |f| f.puts application_controller_contents }
128
+
129
+ # Need this to create the table (which will be in SQLite by default, which is easy), or else
130
+ # the controller actions will fail since there will be no such table.
131
+ rails_server.run_command_in_rails_root!("rake db:migrate")
132
+
133
+ ensure_file_matches!('app/views/my_models/index.html.rb', %r{class Views::MyModels::Index < Views::Base})
134
+ ensure_file_matches!('app/views/my_models/show.html.rb', %r{class Views::MyModels::Show < Views::Base})
135
+ ensure_file_matches!('app/views/my_models/edit.html.rb', %r{class Views::MyModels::Edit < Views::Base})
136
+ ensure_file_matches!('app/views/my_models/new.html.rb', %r{class Views::MyModels::New < Views::Base})
137
+ ensure_file_matches!('app/views/my_models/form.html.rb', %r{class Views::MyModels::Form < Views::Base})
138
+
139
+ # Ruby 1.8.7 and Rails 3.0.x seems to have issues unless we do this, sadly...
140
+ rails_server.stop!
141
+ rails_server.start!
142
+
143
+ # This won't check that the views all have the right HTML in them (that's nearly impossible without
144
+ # just duplicating exactly what they're supposed to contain, right here), but it will check that they
145
+ # compile and produce some kind of HTML output.
146
+ ensure_action_matches!('my_models', %r{<h1>\s*My Models\s*</h1>}m)
147
+ new_html = ensure_action_matches!('my_models/new', %r{<form.*action=["']/my_models["']}m)
148
+
149
+ # Now, we try to create one
150
+ response = rails_server.post('my_models', :post_variables => {
151
+ 'my_model[foo]' => 'foo1', 'my_model[bar]' => 23456, :commit => 'Create My model' },
152
+ :ignore_status_code => true)
153
+
154
+ new_url = if (300..399).include?(Integer(response.code))
155
+ response['Location']
156
+ else
157
+ raise "Response didn't seem to give us a redirect: #{response.code.inspect} (from #{response.inspect})"
158
+ end
159
+ path = URI.parse(new_url).path
160
+
161
+ ensure_action_matches!(path, %r{foo1.*23456}m)
162
+ ensure_action_matches!("#{path}/edit", %r{Editing.*foo1.*23456}m)
163
+ ensure_action_matches!("my_models", %r{<h1>\s*My Models\s*</h1>.*foo1.*23456}m)
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,11 @@
1
+ class CarryoverController < ApplicationController
2
+ def show
3
+ # nothing here
4
+ @the_id = params[:id]
5
+ end
6
+
7
+ def edit
8
+ # nothing here
9
+ @the_id = params[:id]
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class Views::Carryover::Show < Fortitude::Widgets::Html5
2
+ needs :the_id
3
+
4
+ def content
5
+ h1 "Show Carryover #{the_id}"
6
+
7
+ p "Edit: #{edit_carryover_path}"
8
+ end
9
+ end
@@ -5,6 +5,8 @@ class Views::ComplexHelpersSystemSpec::FormForTest < Fortitude::Widgets::Html5
5
5
  text "INSIDE_BEFORE"
6
6
  text "FIRST: "
7
7
  f.text_field :first_name
8
+ text "MIDDLE: "
9
+ f.send(:text_field, :middle_name)
8
10
  text "LAST: "
9
11
  f.text_field :last_name
10
12
  text "INSIDE_AFTER"