effigy 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -2,35 +2,25 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'rake/gempackagetask'
4
4
 
5
- require 'spec/rake/spectask'
6
-
7
5
  desc 'Default: run the specs and metrics.'
8
6
  task :default => [:spec, :metrics]
9
7
 
10
- Spec::Rake::SpecTask.new do |t|
11
- t.spec_opts = ['--color', '--format', 'progress']
12
- t.libs = %w(spec)
13
- t.ruby_opts = ['-rrubygems']
14
- end
8
+ begin
9
+ require 'spec/rake/spectask'
15
10
 
16
- task :rails_root do
17
- rails_root = File.join('tmp', 'rails_root')
18
- unless File.exist?(rails_root)
19
- FileUtils.mkdir_p(File.dirname(rails_root))
20
- command = "rails #{rails_root}"
21
- output = `#{command} 2>&1`
22
- if $? == 0
23
- FileUtils.ln_s(FileUtils.pwd, File.join(rails_root, 'vendor', 'plugins'))
24
- else
25
- $stderr.puts "Command failed with status #{$?}:"
26
- $stderr.puts command
27
- $stderr.puts output
28
- end
11
+ Spec::Rake::SpecTask.new do |t|
12
+ t.spec_opts = ['--color', '--format', 'progress']
13
+ t.libs = %w(spec)
14
+ t.ruby_opts = ['-rrubygems']
29
15
  end
30
- end
31
-
32
16
 
33
- task :spec => :rails_root
17
+ task :spec => :rails_root
18
+ rescue LoadError => exception
19
+ puts "Missing dependencies for specs"
20
+ task :spec do
21
+ raise exception
22
+ end
23
+ end
34
24
 
35
25
  desc "Remove build files"
36
26
  task :clean do
@@ -60,34 +50,37 @@ begin
60
50
  end
61
51
  Jeweler::GemcutterTasks.new
62
52
  rescue LoadError
63
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
53
+ puts "Missing dependencies for jeweler"
64
54
  end
65
55
 
66
- begin
67
- require 'reek/adapters/rake_task'
68
-
69
- namespace :metrics do
70
- desc "Run reek"
71
- Reek::RakeTask.new do |t|
72
- t.source_files = FileList['lib/**/*.rb', 'rails/**/*.rb']
73
- t.fail_on_error = false
56
+ namespace :metrics do
57
+ desc "Run reek"
58
+ begin
59
+ require 'reek/adapters/rake_task'
60
+ task :reek do
61
+ files = FileList['lib/**/*.rb', 'rails/**/*.rb'].to_a.join(' ')
62
+ system("reek -q #{files}")
63
+ end
64
+ rescue LoadError => exception
65
+ puts "Missing dependencies for metrics."
66
+ task :reek do
67
+ puts exception.inspect
74
68
  end
75
69
  end
76
-
77
- desc "Run all metrics"
78
- task :metrics => ['metrics:reek']
79
- rescue LoadError => e
80
- puts e.inspect
81
- puts "Missing dependencies for metrics."
82
70
  end
83
71
 
72
+ desc "Run all metrics"
73
+ task :metrics => ['metrics:reek']
74
+
84
75
  begin
85
76
  require 'yard'
86
77
 
87
78
  YARD::Rake::YardocTask.new do |t|
88
79
  t.files = ['lib/**/*.rb', 'rails/**/*.rb']
89
80
  end
90
- rescue LoadError => e
91
- puts e.inspect
81
+ rescue LoadError => exception
92
82
  puts "Missing dependencies for yard."
83
+ task :yard do
84
+ raise exception
85
+ end
93
86
  end
data/TODO.textile CHANGED
@@ -1,5 +1,3 @@
1
1
  * Implement more jQuery manipulation methods
2
- * Handle Rails layouts specially
3
- * Find a replacement for Rails partials
4
2
  * Include ActionView helpers that don't generate markup
5
3
  * Replace ActionView helpers taht generate markup
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.3.0
data/lib/effigy/rails.rb CHANGED
@@ -3,4 +3,61 @@ require 'effigy/view'
3
3
  require 'effigy/rails/view'
4
4
  require 'effigy/rails/template_handler'
5
5
 
6
+ module Effigy
7
+ # Rails-specific functionality.
8
+ #
9
+ # Effigy includes Rails generators for generating effigy view and template
10
+ # files within Rails projects, as well as a Rails-specific view superclass
11
+ # that provides functionality like assigns, layouts, and partials.
12
+ #
13
+ # Example:
14
+ #
15
+ # <pre>
16
+ # # app/controllers/magic_controller.rb
17
+ # class MagicController < ApplicationController
18
+ # def index
19
+ # @spell = 'hocus pocus'
20
+ # end
21
+ # end
22
+ # </pre>
23
+ #
24
+ # <pre>
25
+ # # app/views/magic/index.html.effigy
26
+ # class MagicIndexView < Effigy::Rails::View
27
+ # def transform
28
+ # text('h1', @spell)
29
+ # end
30
+ # end
31
+ # </pre>
32
+ #
33
+ # <pre>
34
+ # # app/templates/magic/index.html
35
+ # <h1>Spell name goes here</h1>
36
+ # </pre>
37
+ #
38
+ # View this example in your browser and you'll see "hocus pocus."
39
+ #
40
+ # == Generators
41
+ #
42
+ # Example:
43
+ # ./script/generate effigy_view users new edit index
44
+ #
45
+ # This will generate Effigy views and templates for the "new," "edit," and
46
+ # "index," actions of UsersController, such as
47
+ # app/views/users/new.html.effigy, and app/templates/users/new.html.
48
+ #
49
+ # == Rendering Effigy views from Rails
50
+ #
51
+ # Effigy includes a Rails template handler, so you can render effigy views as normal.
52
+ # Rendering the "index" action from "UsersController" will look for a
53
+ # UsersIndexView class in app/views/users/index.html.effigy, and use it to
54
+ # transform app/templates/users/index.html.
55
+ #
56
+ # == Effigy Rails views
57
+ #
58
+ # See {Effigy::Rails::View} for extra methods available to Rails views.
59
+ module Rails
60
+ end
61
+ end
62
+
6
63
  ActionView::Template.register_template_handler :effigy, Effigy::Rails::TemplateHandler
@@ -1,8 +1,24 @@
1
1
  module Effigy
2
2
  module Rails
3
+ # Adds hooks to Rails to discover Effigy views and templates.
4
+ #
5
+ # View files should be added to the app/views/<controller> directory with
6
+ # an .effigy suffix. Template files should be added to
7
+ # app/templates/<controller> with no suffix.
8
+ #
9
+ # For example, the view and template for PostsController#new would be
10
+ # app/views/posts/new.html.effigy and app/templates/posts/new.html,
11
+ # respectively.
12
+ #
13
+ # You can use the packaged generators to create these files.
14
+ #
15
+ # See {Effigy::Rails} for more information about generators.
3
16
  class TemplateHandler < ActionView::TemplateHandler
4
17
  include ActionView::TemplateHandlers::Compilable
5
18
 
19
+ # Compiles the given view. Calls by ActionView when loading the view.
20
+ # @return [String] Ruby code that can be evaluated to get the rendered
21
+ # contents of this view
6
22
  def compile(view)
7
23
  @view = view
8
24
  load_view_class
@@ -13,28 +29,73 @@ module Effigy
13
29
  assigns = variables.inject({}) do |hash, name|
14
30
  hash.update(name => @controller.instance_variable_get(name))
15
31
  end
32
+ local_assigns.each do |name, value|
33
+ assigns.update("@\#{name}" => value)
34
+ end
16
35
  end
17
- view = #{view_class_name}.new(assigns) { |*names| yield(*names) }
36
+ view = #{view_class_name}.new(self, assigns) { |*names| yield(*names) }
18
37
  view.render(#{template_source.inspect})
19
38
  RUBY
20
39
  end
21
40
 
41
+ # @return [String] the name of the view, such as "index"
22
42
  def view_name
23
43
  @view.name
24
44
  end
25
45
 
46
+ # @return [String] the path from the view root to the view file. For
47
+ # example, "RAILS_ROOT/app/views/users/index.html.effigy" would be
48
+ # "users."
26
49
  def base_path
27
50
  @view.base_path
28
51
  end
29
52
 
53
+ # Loads the view class from the discovered view file. View classes should
54
+ # be named after the controller and action, such as UsersIndexView.
55
+ #
56
+ # See {#view_class_name} for more information about class names.
30
57
  def load_view_class
31
58
  load(@view.filename)
32
59
  end
33
60
 
61
+ # Generates a class name for this view. Normal views are prefixed with
62
+ # the controller namd and suffixed with "View," such as "PostsEditView"
63
+ # for app/views/posts/edit.html.effigy. Partials are prefixed with the
64
+ # controller and suffixed with "Partial," such as "PostsPostPartial" for
65
+ # app/views/posts/_post.html.effigy. Layouts are suffixed with "Layout,"
66
+ # such as "ApplicationLayout" for
67
+ # app/views/layouts/application.html.effigy.
34
68
  def view_class_name
35
- [base_path, view_name, 'view'].join('_').camelize
69
+ view_class_components.join('_').camelize.sub(/^Layouts/, '')
70
+ end
71
+
72
+ # @return [Array] the components that make up the class name for this view
73
+ def view_class_components
74
+ [base_path, view_name.sub(/^_/, ''), view_class_suffix]
75
+ end
76
+
77
+ # @return [String] the suffix for this view based on the type of view
78
+ def view_class_suffix
79
+ if layout?
80
+ 'layout'
81
+ elsif partial?
82
+ 'partial'
83
+ else
84
+ 'view'
85
+ end
86
+ end
87
+
88
+ # @return [Boolean] true-ish if this view is a layout, false-ish otherwise
89
+ def layout?
90
+ base_path =~ /^layouts/
91
+ end
92
+
93
+ # @return [Boolean] true-ish if this view is a partial, false-ish otherwise
94
+ def partial?
95
+ @view.name =~ /^_/
36
96
  end
37
97
 
98
+ # @return [String] the contents of the template file for this view
38
99
  def template_source
39
100
  template_path = @view.load_path.path.sub(/\/views$/, '/templates')
40
101
  template_file_name = File.join(template_path, base_path, "#{view_name}.#{@view.format}")
@@ -1,15 +1,52 @@
1
1
  module Effigy
2
2
  module Rails
3
+ # Provides Rails-specific methods to Effigy views. Rather than
4
+ # instantiating this class directly, it is recommended that you create view
5
+ # and template files and allow {Effigy::Rails::TemplateHandler} to discover
6
+ # and compile views.
7
+ #
8
+ # Instance variables from controller actions will be copied to the view.
3
9
  class View < ::Effigy::View
4
- def initialize(assigns, &layout_block)
10
+
11
+ # [ActionView::Base] the instance that is rendering this view. This
12
+ # instance is used to render partials and access other information about
13
+ # the action being rendered.
14
+ attr_reader :action_view
15
+
16
+ # The passed block will be called to access content captured by
17
+ # content_for, such as layout contents.
18
+ #
19
+ # @param [ActionView::Base] action_view the instance that is rendering
20
+ # this view. See the action_view attribute.
21
+ # @param [Hash] assigns a hash of instance variables to be copied. Names
22
+ # should include the "@" prefix.
23
+ def initialize(action_view, assigns, &layout_block)
24
+
25
+ @action_view = action_view
5
26
  assigns.each do |name, value|
6
27
  instance_variable_set(name, value)
7
28
  end
8
29
  @layout_block = layout_block
9
30
  end
10
31
 
11
- protected
32
+ # Renders the given partial and returns the generated markup.
33
+ #
34
+ # @param [String] name the name of the partial to render, as given to
35
+ # ActionView::Base#render
36
+ # @param [Hash] options
37
+ # @option options [Hash] :locals a hash of extra variables to be assigned
38
+ # on the partial view
39
+ # @return [String] the rendered contents from the partial
40
+ def partial(name, options = {})
41
+ options[:partial] = name
42
+ action_view.render(options)
43
+ end
12
44
 
45
+ # Returns the captured content of the given name. Use "layout" as a name
46
+ # to access the contents for the layout.
47
+ #
48
+ # @param [Symbol] capture the name of the captured content to return
49
+ # @return [String] the captured content of the given name
13
50
  def content_for(capture)
14
51
  @layout_block.call(capture)
15
52
  end
@@ -8,10 +8,42 @@ require 'nokogiri'
8
8
  describe "a controller with an effigy view and template" do
9
9
  before do
10
10
  @files = []
11
+ end
12
+
13
+ after do
14
+ @files.each do |file|
15
+ FileUtils.rm(file)
16
+ end
17
+ end
18
+
19
+ def create_rails_file(relative_path, contents)
20
+ absolute_path = File.join(RAILS_ROOT, relative_path)
21
+ FileUtils.mkdir_p(File.dirname(absolute_path))
22
+ File.open(absolute_path, 'w') { |file| file.write(contents) }
23
+ @files << absolute_path
24
+ absolute_path
25
+ end
26
+
27
+ def create_rails_source_file(relative_path, contents)
28
+ load create_rails_file(relative_path, contents)
29
+ end
30
+
31
+ def render(controller, action = :index)
32
+ @controller = controller
33
+ class << @controller
34
+ include ActionController::TestCase::RaiseActionExceptions
35
+ end
36
+ @request ||= ActionController::TestRequest.new
37
+ @response ||= ActionController::TestResponse.new
38
+ get :index
39
+ @response
40
+ end
41
+
42
+ include ActionController::TestProcess
43
+
44
+ it "should use the view to render the template" do
11
45
  create_rails_source_file 'app/controllers/magic_controller.rb', <<-RUBY
12
46
  class MagicController < ApplicationController
13
- layout 'application'
14
- include ActionController::TestCase::RaiseActionExceptions
15
47
  def index
16
48
  @spell = 'hocus pocus'
17
49
  render
@@ -31,8 +63,32 @@ describe "a controller with an effigy view and template" do
31
63
  <h1 class="success">placeholder title</h1>
32
64
  HTML
33
65
 
66
+ response = render(MagicController.new)
67
+
68
+ response.should be_success
69
+ response.rendered[:template].to_s.should == 'magic/index.html.effigy'
70
+ assigns(:spell).should_not be_nil
71
+ response.body.should have_selector('h1.success', :contents => assigns(:spell))
72
+ end
73
+
74
+ it "should render an effigy layout" do
75
+ create_rails_source_file 'app/controllers/magic_controller.rb', <<-RUBY
76
+ class MagicController < ApplicationController
77
+ layout 'application'
78
+ end
79
+ RUBY
80
+
81
+ create_rails_file 'app/views/magic/index.html.effigy', <<-RUBY
82
+ class MagicIndexView < Effigy::Rails::View
83
+ end
84
+ RUBY
85
+
86
+ create_rails_file 'app/templates/magic/index.html', <<-HTML
87
+ <h1 class="success">title</h1>
88
+ HTML
89
+
34
90
  create_rails_file 'app/views/layouts/application.html.effigy', <<-RUBY
35
- class LayoutsApplicationView < Effigy::Rails::View
91
+ class ApplicationLayout < Effigy::Rails::View
36
92
  def transform
37
93
  html('body', content_for(:layout))
38
94
  end
@@ -43,47 +99,48 @@ describe "a controller with an effigy view and template" do
43
99
  <html><body></body></html>
44
100
  HTML
45
101
 
46
- @controller = MagicController.new
47
- @request = ActionController::TestRequest.new
48
- @response = ActionController::TestResponse.new
49
- end
102
+ response = render(MagicController.new)
50
103
 
51
- after do
52
- @files.each do |file|
53
- FileUtils.rm(file)
54
- end
104
+ response.should be_success
105
+ response.body.should have_selector('html body h1.success')
55
106
  end
56
107
 
57
- def create_rails_file(relative_path, contents)
58
- absolute_path = File.join(RAILS_ROOT, relative_path)
59
- FileUtils.mkdir_p(File.dirname(absolute_path))
60
- File.open(absolute_path, 'w') { |file| file.write(contents) }
61
- @files << absolute_path
62
- absolute_path
63
- end
108
+ it "should render an effigy partial" do
109
+ create_rails_source_file 'app/controllers/magic_controller.rb', <<-RUBY
110
+ class WandController < ApplicationController
111
+ end
112
+ RUBY
64
113
 
65
- def create_rails_source_file(relative_path, contents)
66
- load create_rails_file(relative_path, contents)
67
- end
114
+ create_rails_file 'app/views/wand/index.html.effigy', <<-RUBY
115
+ class WandIndexView < Effigy::Rails::View
116
+ def transform
117
+ replace_with('p', partial('spell', :locals => { :name => 'hocus pocus' }))
118
+ end
119
+ end
120
+ RUBY
68
121
 
69
- def render
70
- get :index
71
- end
122
+ create_rails_file 'app/templates/wand/index.html', <<-HTML
123
+ <html><body>
124
+ <h1 class="success">spell</h1>
125
+ <p>placeholder</p>
126
+ </body></html>
127
+ HTML
72
128
 
73
- include ActionController::TestProcess
129
+ create_rails_file 'app/views/wand/_spell.html.effigy', <<-RUBY
130
+ class WandSpellPartial < Effigy::Rails::View
131
+ def transform
132
+ text('p', @name)
133
+ end
134
+ end
135
+ RUBY
74
136
 
75
- it "should use the view to render the template" do
76
- render
77
- @response.should be_success
78
- @response.rendered[:template].to_s.should == 'magic/index.html.effigy'
79
- assigns(:spell).should_not be_nil
80
- @response.body.should have_selector('h1.success', :contents => assigns(:spell))
81
- end
137
+ create_rails_file 'app/templates/wand/_spell.html', <<-HTML
138
+ <p>put a spell on me</p>
139
+ HTML
82
140
 
83
- it "should render an effigy layout" do
84
- render
141
+ response = render(WandController.new)
85
142
 
86
- @response.should be_success
87
- @response.body.should have_selector('html body h1.success')
143
+ response.should be_success
144
+ response.body.should have_selector('html body p', :contents => 'hocus pocus')
88
145
  end
89
146
  end
@@ -1,18 +1,44 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "script/generate effigy_view users create" do
4
- before do
5
- @controller_name = 'users'
6
- @view_name = 'create'
7
- @view_class_name = 'UsersCreateView'
3
+ module TemplateMatchers
4
+ def contain(expected_text)
5
+ simple_matcher("contain the following lines:\n#{expected_text}") do |path, matcher|
6
+ if File.exist?(path)
7
+ actual_text = IO.read(path)
8
+ if actual_text.include?(expected_text)
9
+ true
10
+ else
11
+ matcher.failure_message =
12
+ "Expected to get the following text:\n#{expected_text}\nBut got:\n#{actual_text}"
13
+ false
14
+ end
15
+ else
16
+ matcher.failure_message = "File does not exist"
17
+ false
18
+ end
19
+ end
20
+ end
21
+
22
+ def rails_command(command)
8
23
  FileUtils.cd RAILS_ROOT do
9
- command = "script/generate effigy_view --backtrace #{@controller_name} #{@view_name} 2>&1"
10
- output = `#{command}`
24
+ output = `#{command} 2>&1`
11
25
  unless $? == 0
12
26
  violated "Command failed: #{command}\n#{output}"
13
27
  end
14
28
  end
15
29
  end
30
+ end
31
+
32
+ describe "script/generate effigy_view users create" do
33
+
34
+ include TemplateMatchers
35
+
36
+ before do
37
+ @controller_name = 'users'
38
+ @view_name = 'create'
39
+ @view_class_name = 'UsersCreateView'
40
+ rails_command "script/generate effigy_view --backtrace #{@controller_name} #{@view_name} 2>&1"
41
+ end
16
42
 
17
43
  after do
18
44
  FileUtils.rm_f(view_path)
@@ -49,22 +75,55 @@ describe "script/generate effigy_view users create" do
49
75
  File.join('app', 'templates', @controller_name, "#{@view_name}.html")
50
76
  end
51
77
 
52
- def contain(expected_text)
53
- simple_matcher("contain the following lines:\n#{expected_text}") do |path, matcher|
54
- if File.exist?(path)
55
- actual_text = IO.read(path)
56
- if actual_text.include?(expected_text)
57
- true
58
- else
59
- matcher.failure_message =
60
- "Expected to get the following text:\n#{expected_text}\nBut got:\n#{actual_text}"
61
- false
62
- end
63
- else
64
- matcher.failure_message = "File does not exist"
65
- false
66
- end
67
- end
78
+ end
79
+
80
+ describe "script/generate effigy_view layouts narrow" do
81
+
82
+ include TemplateMatchers
83
+
84
+ before do
85
+ @layout_name = 'narrow'
86
+ @layout_class_name = 'NarrowLayout'
87
+ rails_command "script/generate effigy_view --backtrace layouts #{@layout_name} 2>&1"
88
+ end
89
+
90
+ after do
91
+ FileUtils.rm_f(view_path)
92
+ FileUtils.rm_f(template_path)
93
+ end
94
+
95
+ it "should create a view file" do
96
+ view_path.should contain("class #{@layout_class_name} < Rails::Effigy::View")
97
+ view_path.should contain("private")
98
+ view_path.should contain("def transform")
99
+ view_path.should contain("html('body', content_for(:layout))")
100
+ view_path.should contain(relative_template_path)
101
+ view_path.should contain("end\nend")
102
+ end
103
+
104
+ it "should create a template file" do
105
+ template_path.should contain("<html>")
106
+ template_path.should contain("<body>")
107
+ template_path.should contain("<p>Edit me at #{relative_template_path}</p>")
108
+ template_path.should contain("<p>Edit my view at #{relative_view_path}</p>")
109
+ template_path.should contain("</body>")
110
+ template_path.should contain("</html>")
111
+ end
112
+
113
+ def view_path
114
+ File.join(RAILS_ROOT, relative_view_path)
115
+ end
116
+
117
+ def relative_view_path
118
+ File.join('app', 'views', 'layouts', "#{@layout_name}.html.effigy")
119
+ end
120
+
121
+ def template_path
122
+ File.join(RAILS_ROOT, relative_template_path)
123
+ end
124
+
125
+ def relative_template_path
126
+ File.join('app', 'templates', 'layouts', "#{@layout_name}.html")
68
127
  end
69
128
 
70
129
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effigy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Ferris
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-04 00:00:00 -05:00
12
+ date: 2009-12-01 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency