effigy 0.2.1 → 0.3.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.
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