As rails 5.1+ switched to erubi I tried to use that in ruby script:
require 'erubi'
template = Erubi::Engine.new("<%= test %>", escape: true)
However I'm stacked trying to render that template to html.
erubi source code: https://github.com/jeremyevans/erubi
erubi is fork of erubis, and in erubis the rendering is done via result method:
require 'erubis'
template = Erubis::Eruby.new("<%= test %>", escape: true)
template.result test: "<br>here" #=> "<br>here"
However there's no result method in erubi.
From the Erubi README (it says “for a file” but it appears to mean “for a template”):
Erubi only has built in support for retrieving the generated source for a file:
require 'erubi'
eval(Erubi::Engine.new(File.read('filename.erb')).src)
So you will need to use one of the eval variants to run from a standalone script.
template = Erubi::Engine.new("7 + 7 = <%= 7 + 7 %>")
puts eval(template.src)
Outputs 7 + 7 = 14.
If you want to be able to use instance variables in your template as you might be used to from Rails, Sinatra etc., you will need to create a context object and use instance_eval:
class Context
attr_accessor :message
end
template = Erubi::Engine.new("Message is: <%= #message %>")
context = Context.new
context.message = "Hello"
puts context.instance_eval(template.src)
Outputs Message is: Hello.
In rails 5.1 I switched out the Erubis::Eruby.new code to the following:
ActionController::Base.render(inline: "<%= test %>", locals: {test: "<br>here"})
Rails will do the heavy lifting.
Related
I frequently use Sinatra for smallish projects. It's pretty good for what I need but I miss the ability to mark strings as HTML safe, and then ERB knowing when to escape or not escape accordingly.
I'd really like it if I could rip out the patches that Rails makes to Erubi (around here) and then apply those patches to Erubi myself so that tilt can just use the monkey-patched Erubi and everyone lives happily ever after. However, after digging around in the source, it's not clear to me how I could actually accomplish that.
I also tried to find some way to get an interface into ActionView like the render method, but I couldn't even find where that was defined.
How can I use ActionView outside of Rails, ideally by using ActionView's monkey-patches to Erubi, or if that won't work, how else can I use ActionView to go from template string to rendered string outside Rails?
Specifically, I'd like to be able to do the following:
def some_wrapper_func(unescaped_html)
"<div>#{h unescaped_html}</div>".html_safe
end
# test1.erb
hello world <%= "<script>alert('hi');</script>" %> <%= some_wrapper_func("<span>foobar</span>") %>
#=> hello world <script>alert('hi');</script> <div><span>foobar</span></div>
What you need here is ActiveSupport. I'm not sure if it is overkill or not, but you can do this:
#app.rb:
require 'sinatra'
require 'active_support/all'
get '/' do
erb :index
end
And in a view:
#views/index.erb
Hello, world!
<%= "<script>alert('Hello!')</script>".html_safe %>
Mind that requre 'active_support' will load nothing and requre 'active_support' will load all modules. You can specify what modules do need as described
in Active Support Core Extensions.
If the only goal is to enable auto-escaping, there is no need for ActionView at all. It can be done like this (mind the <%== %> tag):
#app.rb
require 'sinatra'
require 'erubis'
set :erb, :escape_html => true
get '/' do
erb :index
end
#View
<%= "<script>alert('Hello, and it will not produce alert!')</script>" %>
<%== "<script>alert('Hello and it will!')</script>" %>
We will try to get ActionView up and running with Sinatra (or any Ruby program):
require 'sinatra'
require 'action_view'
get '/' do
av_render :index
end
def av_render view
paths = ActionView::PathSet.new(["views"])
lookup_context = ActionView::LookupContext.new(paths)
renderer = ActionView::Renderer.new(lookup_context)
view_context = ActionView::Base.new(renderer)
renderer.render(view_context, template: view)
end
And in the view we use html_safe:
<%= "<script>alert('Hello, and it will not produce alert!')</script>" %>
<%= "<script>alert('Hello and it will!')</script>".html_safe %>
Wrapper functions also work with this approach. The only problem here is a custom-render method, but it can be avoided.
If you'd like to avoid ActionView entirely and just use Tilt+Erubi, you can actually create for yourself a SafeString class and have Erubi use it for compilation.
Erubi takes some important options, specifically:
- escape: If this is true, then <%= %> will escape by default, otherwise only <%== %> will escape by default
- bufval: Internally, erubi uses what is basically an accumulator to build up your template. This is the value that it will initialize that accumulator to. It is important that it has a <<(str) method to concat new pieces on, and a to_s method to get the return value out.
- escapefunc: The function that Erubi will use for escaping. It's important to override this, because we'll want to escape anything that isn't a SafeString but let SafeStrings pass through unchanged.
So, first let's define this SafeString class:
# main.rb
require 'tilt'
require 'erubi'
class SafeString
def initialize(str = '')
#str = str
end
def <<(str)
if str.is_a? String
return (#str << str)
elsif str.is_a? SafeString
#str = #str << str
return self
else
throw "Can't concat"
end
end
def to_s
#str
end
def self.escape(val)
if val.is_a? SafeString
return val.to_s
else
return Erubi.h(val.to_s)
end
end
module Helpers
def raw(content)
SafeString.new(content)
end
end
end
Then, we'll need to include the raw helper we defined and to test it on an ERB file:
include SafeString::Helpers
puts Tilt::ErubiTemplate.new("somefile.erb", bufval: 'SafeString.new', escapefunc: 'SafeString.escape', escape: true).render
# somefile.erb
<%= "<script>alert('Hello, and it will not produce alert!')</script>" %>
<%= raw("<script>alert('Hello and it will!')</script>") %>
And this will give us the output we desire!
# stdout
<script>alert('Hello, and it will not produce alert!')</script>
<script>alert('Hello and it will!')</script>
To improve this, instead of this minimal SafeString class, you could use ActiveSupport::SafeBuffer.
i'm still fairly new to server side scripts and try myself a little bit on ruby to write me little helpers and to learn some new things.
I currently try to write a small ruby app which sends a json file of all images within a specific folder to my page where i can use those to handle them further in js.
I read quite a few introductions to ruby and rails and got a recommendation to look into rack as a lightweight communicator between server and app.
While the ruby part works fine, i have difficulties to understand how to send out the generated JSON as a reaction to a future ajax call (e.g.). Hope someone can give me a few hints or sources to look into for further understanding. Thanks!
require 'json'
class listImages
def call(env)
imageDir = Dir.chdir("./img");
files = Dir.glob("img*")
n = 0
tempHash = {}
files.each do |i|
tempHash["img#{n}"] = i
n += 1
end
File.open("temp.json","w") do |f|
f.write(tempHash.to_json)
end
[200,{"Content-Type" => "application/javascript"}, ["temp.json"]]
end
puts "All done!"
end
run listImages.new
if $0 == __FILE__
require 'rack'
Rack::Handler::WEBrick.run MyApp.new
end
You don't have to save the JSON to a file before you can send it. Just send it directly:
[200, {"Content-Type" => "application/json"}, [tempHash.to_json]]
With your current code, you are only sending the String "temp.json".
That said, the rest of your code looks a little bit messy/not conform Ruby coding standards:
Start your classnames with an uppercase: class ListImages, not class listImages.
Use underscores, not camelcase for variable names: image_dir, not imageDir.
The puts "All done!" statement is outside the method definition and will be called early, when the class is loaded.
You define a class ListImages but in the last line of your code you refer to MyApp.
I would like to create a basic ruby script that renders Slim templates into html (This would eventually be part of a larger project). Ideally I would like to use the HTML produced within the script.
I understand this is possible using TILT (as shown in the SLIM README) where it says the following:
Slim uses Tilt to compile the generated code. If you want to use the Slim template directly, you can use the Tilt interface.
Tilt.new['template.slim'].render(scope)
Slim::Template.new('template.slim', optional_option_hash).render(scope)
Slim::Template.new(optional_option_hash) { source }.render(scope)
The optional option hash can have to options which were documented in the section above. The scope is the object in which the template code is executed.
However, I'm still unable to successfully run this. Therefore, I was wondering if someone could help me by producing a working example.
EDIT (this has recently been edited further ):
I have played about with the code quite a bit but I keep on getting the following error:
undefined local variable or method `source' for main:Object (NameError)
This is what i'm running:
require 'slim'
# I'm not sure about the next two lines...
optional_option_hash = {}
scope = Object.new
Tilt.new('template.slim').render(scope)
Slim::Template.new('template.slim', optional_option_hash).render(scope)
Slim::Template.new(optional_option_hash) { source }.render(scope)
Many Thanks for all your help.
See Specifying a layout and a template in a standalone (not rails) ruby app, using slim or haml
This is what I ended up using:
require 'slim'
# Simple class to represent an environment
class Env
attr_accessor :name
end
scope = Env.new
scope.name = "test this layout"
layout =<<EOS
h1 Hello
.content
= yield
EOS
contents =<<EOS
= name
EOS
layout = Slim::Template.new { layout }
content = Slim::Template.new { contents }.render(scope)
puts layout.render{ content }
For the scope, you can put in modules/classes or even self.
Quick essentials of
module SlimRender
def slim(template, variables = {})
template = template.to_s
template += '.slim' unless template.end_with? '.slim'
template = File.read("#{ROOT}/app/views/#{template}", encoding: 'UTF-8')
Slim::Template.new { template }.render OpenStruct.new(variables)
end
end
Include SlimRender to your class and:
def render_something
slim 'streams/scoreboard', scores: '1-2'
end
I'm working on a Sinatra app and want to write my own form helpers. In my erb file I want to use the rails 2.3 style syntax and pass a block to a form_helper method:
<% form_helper 'action' do |f| %>
<%= f.label 'name' %>
<%= f.field 'name' %>
<%= f.button 'name' %>
<% end %>
Then in my simplified form helper I can create a FormBuilder class and yield the methods to the erb block like so:
module ViewHelpers
class FormBuilder
def label(name)
name
end
def field(name)
name
end
def button(name)
name
end
end
def form_helper(action)
form = FormBuilder.new
yield(form)
end
end
What I don't understand is how to output the surrounding <form></form> tags. Is there a way to append text on only the first and last <%= f.___ %> tags?
Rails has had to use some tricks in order to get block helpers to work as wanted, and they changed moving from Rails 2 to Rails 3 (see the blogposts Simplifying Rails Block Helpers and Block Helpers in Rails 3 for more info).
The form_for helper in Rails 2.3 works by directly writing to the output buffer from the method, using the Rails concat method. In order to do something similar in Sinatra, you’ll need to find a way of writing to the output from your helper in the same way.
Erb works by creating Ruby code that builds up the output in a variable. It also allows you to set the name of this variable, by default it is _erbout (or _buf in Erubis). If you change this to be an instance variable rather than a local variable (i.e. provide a variable name that starts with #) you can access it from helpers. (Rails uses the name #output_buffer).
Sinatra uses Tilt for rendering templates, and Tilt provides an :outvar option for setting the variable name in Erb or Erubis templates.
Here’s an example of how this would work:
# set the name of the output variable
set :erb, :outvar => '#output_buffer'
helpers do
def form_helper
# use the new name to write directly to the output buffer
#output_buffer << "<form>\n"
# yield to the block (this is a simplified example, you'll want
# to yield your FormBuilder object here)
yield
# after the block has returned, write any closing text
#output_buffer << "</form>\n"
end
end
With this (fairly simple) example, an Erb template like this:
<% form_helper do %>
... call other methods here
<% end %>
results in the generated HTML:
<form>
... call other methods here
</form>
I have a ruby class that extends Erubis (a ruby templating engine) and I would like to create my own tags. The following is an example of what i'd like to be reproduce:
<%= link_to "/some/url" %>
This code should generate a html 'a' tag linking to some url. Now i'd like to be able to create my own tags such as:
<%= javascript_file "/some/javascript/file" %>
which would generate a script tag linking to some javascript file of my choice.
How can i easily extend erubis to do that?
Thanks for your time.
Those are just function calls that return the tag in a string:
def javascript_file( file_path )
"<script src=\"#{ file_path }\" type=\"text/javascript\"/>"
end
You just need to ensure that the functions are within scope at the time you call the binding.