Curtain
Curtain is a template rendering framework for Ruby. It is built on top of Tilt. Curtain is not tied to any web framework like Rails or Sinatra, so it can be used standalone in any Ruby project.
Installation
Add this line to your application's Gemfile:
gem 'curtain'
And then execute:
$ bundle
Or install it yourself as:
$ gem install curtain
Usage
Rendering Templates
To use Curtain, you define a view and then have that view render templates:
hello.erb
<h1><%= msg %></h1>
my_view.rb
class MyView
include Curtain
attr_accessor :msg
def initialize(msg)
@msg = msg
end
end
view = MyView.new("Hello, World!")
view.render("hello") # => <h1>Hello, World!</h1>
The template is rendered in the scope of the view object, so any methods defined in the view are available to the template. You don't have to create a subclass if you don't need to:
Curtain::View.new.render("hello", msg: "Hello, World!")
There is an equivalent shortcut available:
Curtain.render("hello", msg: "Hello, World!")
Partials
Because render
is a method on the view object, you can call it from within the template to render one template from another:
main.erb
<%= render "partial" %>
partial.erb
<h2>I'm a Partial!</h2>
Curtain.render("main") # => "<h2>I'm a Partial!</h2>"
You can also pass local variables to the partial:
main.erb
<%= render "partial", greeting: 'Hello' %>
partial.erb
<h2><%= greeting %>, I'm a Partial!</h2>
Curtain.render("main") # => "<h2>Hello, I'm a Partial!</h2>"
Variables
If you don't want to define a subclass of Curtain::View
and add attributes to it, you can also use variables. Curtain::View
supports the hash-like Ruby methods []
and []=
to define variables that will act as locals in when the template is rendered:
hello.erb
<h1><%= msg %></h1>
view = Curtain::View.new
view[:msg] = "Hello"
view.render(:hello) # => "<h1>Hello</h1>"
Note that unlike locals, variables exist throughout nested scope of render calls:
main.erb
foo: <%= foo %>
bar: <%= bar %>
<%= render "partial" %>
partial.erb
foo: <%= foo %>
bar: <%= bar %>
class MainView < Curtain::View
end
view = MainView.new
view[:foo] = "foo"
view.render :bar => "bar"
This example would result in an error. As the main template is first rendered, foo is defined as "foo" because it is a variable, bar is "bar" because it is passed in as a local. Then the partial template is rendered, and foo is still defined as "foo" because it is a variable, but since bar was a local passed to the rendering of main, it doesn't carry through to the rendering of partial.
HTML Tag Methods
NOTE: The HTML Tag Methods are only supported in ERB and Slim
Curtain::View
has HTML tag methods to make generating HTML easier. There is a method for every valid HTML tag. The methods for void tags take an optional Hash of attributes:
photo.erb
<%= img src: image_path %>
Curtain.render('photo', image_path: '/pic.jpg') # => <img src="/pic.jpg">
The methods for tags that may have body content accept the body content as a string in the first argument:
link.erb
<%= a 'Home', href: path %>
Curtain.render('link', path: '/') # => <a href="/">Home</a>
or you can pass the body content as a block:
link.erb
<%= a href: path, class: 'btn btn-default' do %>
<span class="glyphicon glyphicon-home"></span> Home
<% end %>
Curtain.render('link', path: '/') # => <a href="/" class="btn btn-default"><span class="glyphicon glyphicon-home"></span> Home</a>
Form Tag Methods
There are some extra tag methods related to forms, as well as enhanced functionality for the form-related tags.
Input Types
You can create an input
tag using the input
method:
<%= input type: 'text', name: 'first_name' %>
but there are shortcut methods for all the valid types for input. Here are some examples:
<%= text :first_name %>
<%= file :photo %>
<%= email :email %>
<%= password :password %>
<%= url :website %>
The equivalent to those examples are:
<%= input type: 'text', name: 'first_name' %>
<%= input type: 'file', name: 'photo' %>
<%= input type: 'email', name: 'email' %>
<%= input type: 'password', name: 'password' %>
<%= input type: 'url', name: 'website' %>
The one exception to this pattern is that this:
<%= submit "Save" %>
generates:
<button type="submit">Save</button>
Forms
The form
tag has some special behavior as well. You can use it without any special behavior with no arguments:
<%= form do %>
<% end %>
which generates:
<form>
</form>
or you can pass a Hash of attributes:
<%= form method: 'patch', action: '/register' do %>
<% end %>
which generates:
<form method="post" action="/register">
<input type="hidden" name="_method" value="patch">
</form>
Because browsers only natively support GET
and POST
methods, a hidden parameter is generated for the method if you use something other than GET
or POST
.
Form Builders
When creating forms, it is common to want to have a form field for each attribute of an object and have the value of each input set to the value of the correspoding attribute of the object. You can do that using a Form Builder. Assuming you have something like this in the view:
@person = Person.new(id: 1, first_name: 'Paul')
You can use a form builder to create a form using the for
attribute of the form
tag method:
<%= form for: @person do %>
<%= text :first_name %>
<% end %>
This generates:
<form method="post" action="/people/1">
<input type="hidden" name="_method" value="PATCH">
<input type="text" name="person[first_name]" value="Paul">
</form>
There are a few things that happen when you use the for
attribute. First, the method and action are inferred based on the object. If the object has an ID, it is assumed to be a record that already been saved, therefore the method will be patch
and the action will be the pluralized class name with the ID at the end. If the object does not have an ID, it is assumed to be a new record, so the post
method is used and the action is just the pluralized class name. You can override the generated method by passing explict values for method
and action
<%= form for: @person, method: 'post', action: '/profile' do %>
<% end %>
The second thing that happens when you use a form builder is that the input names are prefixed with the underscored name of the class and the attribute is in brackets. This is so that all parameters related to the object will be collected into one Hash in the parameters. In the example above, person
is the underscored name of the class and the attribute is first_name
, so the name of the input ends up being person[first_name]
. If you want to use something other than the underscored name of the class, you can use the as
attributesin the form
method:
<%= form for: @person, as: 'account' do %>
<%= text :first_name %>
<% end %>
This will result in the input name being account[first_name]
.
Finally, the value is set based on the value of the attribute of the object the form is for. As seen in the example above, value
is Paul
, because that is that is the value returned by @person.first_name
.
Fields
When building forms, it is common to want to have a label and errors messages next to each input. This pattern is supported by the _field
methods. This example:
<%= form for: @person do %>
<%= text_field :first_name %>
<% end %>
produces the following HTML:
<form method="post" action="/people">
<div class="form-field">
<label for="first_name">First Name</label>
<input type="text" name="first_name" value="" id="first_name">
<span class="error">is required</span>
</div>
</form>
The content of the label can be customized with the label
attribute. The error only shows up if the form object has an error on that attribute.
There is a corresponding _field
method for each input tag, so text_field
, email_field
, password_field
, etc. There are also _field
methods for the other non-input form tag methods, such as select_field
and textarea_field
.
Custom Form Builders
The markup produced by the _field
methods is controlled by the form builder. The form builder is a class that can be overriden and customized. You can use a different form builder for a specific form:
<%= form builder: MyFormBuilder do %>
The class passed to the builder
attribute should be a subclass of Curtain::FormBuilder
. You can also control which form builder is used in a more broad manner. The default form builder is determined by the default_form_builder
method on the View
class. Curtain::View.default_form_builder
returns Curtain::FormBuilder
. You can set the default form builder on your view like this:
class MyView < Curtain::View
default_form_builder MyFormBuilder
end
This works throughout the inhereitence chain, so you can create a single view in your application that all views inheret from and then set the default_form_builder on that view in order to make all forms in your application use your custom form builder.
Pre-built Form Builders
There are already form builders for the Bootstrap and Foundation CSS frameworks. To use them, install the gem for the one you wish to use, curtain-bootstrap or curtain-foundation, and then set the default form builder on the view you would like to use them on:
class ApplicationView < Curtain::View
default_form_builder Curtain::Bootstrap::FormBuilder
end
See the documentation for each of those projects to see exactly what markup they generate.
Caching
Curtain has built-in support for caching via the cache
method. You use the method like this:
<%= cache 'hourly-stats', expires_in: 1.hour do %>
<% get_report_data.each do |row| %>
...
<% end %>
<% end %>
To use caching, you just set the cache on the view class:
Curtain::View.cache = Dalli::Client.new
You can use anything supported by the Cache library for the cache.
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request