Interpreting content outside ERB tags as unsafe

I drafted this back in 2012… publishing it now in 2016 because I’m tired of all these drafts laying around.

ERB interprets all content outside of ERB tags as safe. That is, ERB does not escape anything outside of ERB tags. Therefore, if you want to put some content into an HTML attribute, you’re forced into putting that content into a string. I find it inconvenient sometimes that I have to put everything into a String. It might end up looking very messy. So, I tried to find a way to simply use the standard ERB approach of putting the content outside of ERB tags.

My first instinct was to use ActionView::Helpers::CaptureHelper.capture to turn the content into a string, which I could then escape. Unfortunately, capture() also interprets the content as safe, and marks the string as such. Using h() has no effect.

After looking at the source of capture() and seeing that it uses a buffer to accumulate the String, I came up with a solution: provide my own buffer which is marked unsafe:

class UnsafeBuffer < ActiveSupport::SafeBuffer
  def html_safe?
   false
  end
end

To use it, pass it to with_output_buffer along with a block that returns a string. Even if the string is safe, the buffer is marked unsafe so the string will end up being escaped. In the example below, I am using capture() to produce a safe string from content outside the ERB tags.

<div data-stuff="
  <%= with_output_buffer(UnsafeBuffer.new) {
    capture do %>
      My <span class="awesome">unsafe content</span>
  <% end } %>
">

Rant about Rspec-Rails

It’s stuff like this that makes Rails and its accompanying toolset a pain in the ass.

If you want a spec to check that your controller rendered a specific thing, you can’t just mock the “controller.render” method. Why? In Rails-land, apparently controller.render is so magical that your mock expectation will receive no arguments, even if your controller passed arguments to the render method. It’ll say, “got: (no args)”

Instead, you have to use the RenderTemplateMatcher with have_rendered or rendered_template (they’re the same).

That, in turn, means you have to actually let the render call complete normally, which may violate the isolation of your controller spec.

Or, if you’re like me, it gives you “ActionView::MissingTemplate: Missing template” even though the template path is accurate. This has something to do with the EmptyTemplatePathSetDecorator mentioned in the error.

At this point, you search the internet until you discover that “By default, controller specs do not render views.” However, strangely, at the same time, “NOTE: unlike rspec-rails-1.x, the template must exist.” Still no good explanation why your completely valid template isn’t being found.

So then you try adding “render_views” to your test, but find that it’s configured to only accept production templates, not template files that you have in your spec directory. So now you have to provide mocks or stubs for all the data that your chosen production template needs. So obviously that’s not a good way to go.

So then you back out the “render_views” statement, and suddenly everything’s working. What?!

Turns out that by default Rspec-Rails doesn’t actually render any templates, but it does look for the template file in the standard file paths (app/views). So don’t try to outsmart Rspec by providing your own view specifically for your spec: it won’t be able to find it.

What a waste of time.

PS: Due to limitations of ActionController::TemplateAssertions$assert_template, which RenderTemplateMatcher delegates to, you can’t check both the template and the layout at the same time. You have to use two separate statements:
response.should have_rendered template
response.should have_rendered layout: layout

Rspec-rails’s rake tasks not loaded in Rails engine

Having trouble getting the Rspec rake tasks to appear when you run “rake -T” in your Rails engine? The dummy app (which you should have if you specified –full) is not pulling in the Bundler dependencies, and as a result the rspec-rails Railtie subclass is not being created.

The fix, for me at least, is changing spec/dummy/config/application.rb line 11 or so to:

Bundler.require(*Rails.groups)

You should now have tasks such as “rake app:spec” available.

You may also need to update your root Rakefile to run the specs by default, by adding “task :default => ‘app:spec'”

Also see my issue on the railties Github

This was with Rails 3.2.8.

Mongoid: NoMethodError: undefined method `sub’ for :section:Symbol

If you recently added a Mongoid relationship that defines a specific “class_name” so that you can have a custom relationship name, and you’re getting an error like

“NoMethodError: undefined method `sub’ for :yourclassname:Symbol”

it might be coming from Mongoid::Relations::Metadata.class_name, which assumes that class_name is a string, not a symbol. Change the class_name on your relationship definition to a string, and it should be fixed.

HTTP Status 418: I’m a Teapot

After seeing this in Rack::Utils (my IDE even highlighted it because it’s the only one in double quotes):

HTTP_STATUS_CODES = {
      100  => 'Continue',
      101  => 'Switching Protocols',
...
      418  => "I'm a Teapot",
...
}

I had to look it up: http://tools.ietf.org/html/rfc2324

Thanks to laserlemon I can finally implement my fully web-controlled coffee pot with Sinatra on Rack!

Origin 1.0.5 yanked

If you’re using Mongoid::Paranoia, and you’re running into a problem that looks like this:


/Users/you/.rvm/gems/ruby-1.9.3-p194/gems/origin-1.0.5/lib/origin/extensions/nil_class.rb:30:in `__expanded__': wrong number of arguments (0 for 1) (ArgumentError)
	from /Users/you/.rvm/gems/ruby-1.9.3-p194/gems/origin-1.0.5/lib/origin/mergeable.rb:199:in `prepare'
	from /Users/you/.rvm/gems/ruby-1.9.3-p194/gems/origin-1.0.5/lib/origin/mergeable.rb:158:in `block in __override__'
	from /Users/you/.rvm/gems/ruby-1.9.3-p194/gems/origin-1.0.5/lib/origin/selectable.rb:597:in `block (2 levels) in selection'
	from /Users/you/.rvm/gems/ruby-1.9.3-p194/gems/origin-1.0.5/lib/origin/selectable.rb:596:in `each_pair'
	from /Users/you/.rvm/gems/ruby-1.9.3-p194/gems/origin-1.0.5/lib/origin/selectable.rb:596:in `block in selection'
	from /Users/you/.rvm/gems/ruby-1.9.3-p194/gems/origin-1.0.5/lib/origin/selectable.rb:594:in `tap'
	from /Users/you/.rvm/gems/ruby-1.9.3-p194/gems/origin-1.0.5/lib/origin/selectable.rb:594:in `selection'
	from /Users/you/.rvm/gems/ruby-1.9.3-p194/gems/origin-1.0.5/lib/origin/mergeable.rb:156:in `__override__'
	from /Users/you/.rvm/gems/ruby-1.9.3-p194/gems/origin-1.0.5/lib/origin/selectable.rb:261:in `ne'
	from (eval):2:in `ne'
	from /Users/you/.rvm/gems/ruby-1.9.3-p194/gems/mongoid-3.0.3/lib/mongoid/paranoia.rb:21:in `block in <module:Paranoia>'
....

It might because you are using version 1.0.5 of the origin gem, which was yanked from rubygems.org yesterday: http://rubygems.org/gems/origin/versions/1.0.5

If that’s the case, you’ll probably want to uninstall bad version:
gem uninstall origin -v 1.0.5

If you’re using Rails 3, you’ll want to make sure that your Gemfile.lock does not refer to origin 1.0.5. You can run “bundle update” or delete your Gemfile.lock and run “bundle” to create a new one.

For more information, take a look at the issue on Github. Good luck!

How to open an existing Rails project in IntelliJ IDEA

As described here, the proper way to open an existing Rails site in IDEA is:

  1. File -> New Project
  2. Create project from scratch (Next)
  3. For “Project files location” choose your Rails application directory, or a parent directory
  4. Leave “Create module” checked, select “Ruby Module” as the type”, and make sure the “content root” & “module file location” is set to the root of your Rails application (Next)
  5. Ensure your Ruby SDK is configured (Next)
  6. Ensure “Ruby on Rails” is checked, and fill out the appropriate settings under “Use existing Rails application” (Finish)

I have used this approach successfully in IntelliJ IDEA 11.0.2 and 11.1.3

If you don’t see Ruby as an option for the module type, ensure you have installed the Ruby plugin.