Liquid templates in rails 4 - ruby-on-rails

Im trying to implement Shopify's liquid templates in my project & render them from the database.
What would be the prettiest & most "railsy" way of doing this, while keeping my controllers free from excessive clutter?
render text: template.render('name' => name).html_safe
seems a bit ugly to me, not to mention possible inheritance logic.

I was in the same situation and wanted to abstract the logic a bit. I ended up with a controller concern with a render_liquid method, that injects some default locals and registers a custom file system to find the templates, etc. It somehow looks like this:
def render_liquid(name, locals, registers = {})
registers[:file_system] = CustomFileSystem.new(self)
template = registers[:file_system].read_template_file(name)
Liquid::Template.parse(template).render!(locals, registers: registers)
end
Eventually my controllers just tell to render a template with specific locals:
render_liquid "page", {
page: PageDrop(#page),
chapter: ChapterDrop(#chapter),
}

Related

Ruby on Rails - Controller without Views

Iam new in Ruby on Rails. Normally I work with other web languages and now (of course) I try to compare it with other languages using on web.
But sometimes i have some problem to understand the philosophy and the character of Ruby on Rails. Of course i understand the concept of MVC.
But now Iam not absolutely sure:
Is ist OK to create and use a controller without views? In some cases you need a "class" for some usefull functionality used by other controllers they have views. Or is it a better style to use a module?
I try to find it out by reading a lot of articles and examples, but didnt find detailed information about this content.
When developing Ruby On Rails apps, it's recommended to put most of your business logic in the models, so for the controllers the logic need to be minimal to provide info for the views, so if it doesn't work with a view chance that you need a controller are really low.
Is it OK to create and use a controller without views
Yep it's okay.
The key thing to keep in mind is that Ruby/Rails is object orientated.
This means that every single you do with your controllers/models etc should have the "object" you're manipulating at its core.
With this in mind, it means you can have a controller without corresponding views, as sometimes, you just need to manipulate an object and return a simple response (for example with Ajax).
--
We often re-use views for different actions (does that count as not having a view):
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def search
render :index, layout: false
end
end
The notion of skinny controller, fat model is sound in principle, you have to account for the times when you may need small pieces of functionality that can only be handled by a controller:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def update
#user = User.find params[:id]
#user.update
respond_to do |format|
format.js {render nothing: true}
format.html
end
end
end
A very rudimentary example of Rails is a drive-thru:
view = input interface
controller = accepts order & delivers
model = gets order packaged etc in backend
There are times when the controller may not need a view (for example, if you update your order with specific dietry requirements), and thus the notion that every controller action has to have a view is false.
It's really about making your controller versatile enough to manage your objects correctly.
Shared controller methods can be placed in ApplicationController. Also, in Rails 4 there are concerns (app/controllers/concerns/) where you can put the modules with methods that can be used by multiple controllers.
Controller handles request and renders corresponding view template. So controller without view is absolutely nonsense.
Every time request will execute this controller, it will just end with missing template error, so you will need to create view folder and put empty files with action name inside of it, which is obviously stupid.

Rails: Render view from outside controller

I'm trying to create an HTML string using a view. I would like to render this from a class that is not a controller. How can I use the rails rendering engine outside a controller? Similar to how ActionMailer does?
Thanks!
Rails 5 and 6 support this in a much more convenient manner that handles creating a request and whatnot behind the scenes:
rendered_string = ApplicationController.render(
template: 'users/show',
assigns: { user: #user }
)
This renders app/views/users/show.html.erb and sets the #user instance variable so you don't need to make any changes to your template. It automatically uses the layout specified in ApplicationController (application.html.erb by default). Full documentation can be found here.
The test shows a handful of additional options and approaches.
You can use ActionView::Base to achieve this.
view = ActionView::Base.new(ActionController::Base.view_paths, {})
view.render(file: 'template.html.erb')
The ActionView::Base initialize takes:
A context, representing the template search paths
An assigns hash, providing the variables for the template
If you would like to include helpers, you can use class_eval to include them:
view.class_eval do
include ApplicationHelper
# any other custom helpers can be included here
end
There is no need to over bloat your app with too many gems. As we know ERB is already included in your Rails app.
#jdf = JDF.new
#job = ERB.new(File.read(Rails.root + "app/views/entries/job.xml.erb"))
result = #job.result(binding)
Above there is snippet of code of an app I'm working on.
#jdf is a object to be evaluated in the erb view.
In my case I needed to render xml.
result is a string to be saved or sent anywhere you like.
In Rails 5:
view = ActionView::Base.new(ActionController::Base.view_paths)
view.render(file: 'template.html.erb')
In Rails 6.1:
lookup_context = ActionView::LookupContext.new(ActionController::Base.view_paths)
context = ActionView::Base.with_empty_template_cache.new(lookup_context, {}, nil)
renderer = ActionView::Renderer.new(lookup_context)
renderer.render(context, { file: 'app/views/template.html.erb' })
ActionView::Renderer.new() takes a lookup_context arg, and render() method takes a context, so we set those up first
ActionView::Base is the default ActiveView context, and must be initialized with with_empty_template_cache method, else render() will error
The {}, nil are required assigns and controller args, which used to default to {}, nil in Rails 5
Rails 6.1 requires a full filepath file: 'app/views/template.html', whereas Rails 5 only required the filename
"I'm trying to create an HTML string using a view." -- If you mean you're in the context of a view template, then just use a helper method or render a partial.
If you're in some other "Plain Old Ruby Object", then keep in mind you're free to use the ERB module directly:
erb = ERB.new("path/to/template")
result = erb.result(binding)
The trick is getting that 'binding' object that gives the context for the code in the template. ActionController and other Rails classes expose it for free, but I couldn't find a reference that explains where it comes from.
http://www.ruby-doc.org/stdlib-2.2.0/libdoc/erb/rdoc/ERB.html#method-i-result
Calling directly render method on ApplicationController may raise error
Helper Devise: could not find the `Warden::Proxy` instance on request environment
Instead we can use ApplicationController.renderer.render like
rendered_string = ApplicationController.renderer.render(
partial: 'users/show',
locals: { user: user }
)
Technically, ActionMailer is a subclass implementation of AbstractController::Base. If you want to implement this functionality on your own, you'll likely want to inherit from AbstractController::Base as well.
There is a good blog post here: https://www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-rails-3/ that explains the steps required.
For future reference, I ended up finding
this handy gem that makes this a breeze:
https://github.com/yappbox/render_anywhere
Best to render the view using a controller, as that's what they're for, and then convert it to a string, as that is your ultimate goal.
require 'open-uri'
html = open(url, &:read)

Use Render template instead of render action by default in rails controller

I'm trying to implement the following behavior:
You can see that i have pretty standart views structure for controller, but instead "actions" partial, i created folder which is named like an action and in them i have a partials for actions.
I Try use "prepend_view_path":
def set_view_paths
self.prepend_view_path ["#{Rails.root}/app/views/#{controller_name}/#{action_name}"]
end
But rails finds next:
Missing template "posts/index"
in ".../app/views/posts/index"
I need to add somthing like that in the end of the action:
render template: 'index'
Question:
Is there a more beautiful way to solve this problem?
Checkout this SO thread. From reading the thread it doesn't look like there is a clean way for you to solve this problem. The only way would be to override render.
I'm not sure why you are putting your template files into subfolders but, if it is not providing you with any benifit, it's probably easier to move index.html.erb, show.html.erb ect out of their subfolders and just put them under views/posts.
This will also make it easier for other developers (and yourself, later on) because you are conforming to Rails conventions.

Create a generic template for JSON from a rails application

I'm writing a rails application with an AngularJS front-end, this is part of a tutorial series I'm writing on connecting rails and angularjs. This means my rails application communicates with the browser exclusively in JSON.
In the angularjs $http documentation it describes a potential json security vulnerability where the json request can be embedded into a script tag, plus some tricky use of jsonp, to allow something akin to a cross-site scripting attack. I've found a few other pages, one in particular I thought described this well, and dates from 2008, so this isn't a new issue.
Apparently this isn't a vulnerability in standard rails json rendering, as rails by default provides back an object containing an array. But when working with angularjs we appear to set root: false (although I have to confess I can't find where I did that, but it's definitely not giving the root node).
Anyway, the bottom line is that the angular documentation recommends prefixing any json response with )]}', so:
['one','two']
Becomes
)]}',
['one','two']
Angular then automatically strips that off again.
I'm looking for a way to do this elegantly. I've seen a lot of questions and answers on stackoverflow about this, but most of those either relate to much earlier versions of rails before JSON handling was more thoroughly embedded, or seem to require me to create a lot of boilerplate code. I'm looking for a method that I can apply to the application controller, or as a helper method, that will work everywhere.
The controller that I'm currently using looks as follows:
class ClubsController < ApplicationController
respond_to :json
# GET /clubs.json
def index
#clubs = Club.all
render json: #clubs
end
end
This doesn't call any templates - the render action skips the templating engine. I can get this working by changing the render line instead to:
respond_with json: #clubs
And creating a template file views/clubs/index.json.erb that contains
)]}',
<%= raw(#clubs.to_json) %>
But I'd then have to create a template for every action on every controller, which feels like boilerplate. I'd like instead to be able to change views/layouts/application.json.erb to have something like:
)]}',
<%= yield %>
But that doesn't work because we only get templating if we call respond_with. And if we call respond_with, we have no way to put the #clubs into the response - so we end up with:
)]}',
As the entirety of the response.
An alternative would perhaps be to override the as_json method to prepend what I want, but that seems a bit like a sledgehammer. Ideally there would be a place I could introduce a helper method, something like:
render prepend_vulnerability_protection(json: #clubs)
So, after all that, two questions:
Is this even a real problem, or does Rails already have some other protection that means I don't need to worry about this at all
Is there a way to do this centrally, or do I need to bite the bullet and create all the boilerplate templates? I can modify the scaffold generators to do it, so it's not the end of the world, but it does seem like a lot of boilerplate code
So, no responses as yet. I'm going to write down what I find from my research, and my current answer.
Firstly, I think this is a genuine vulnerability in rails. Unfortunately the rails and JSON/JSONP area has had some other recent vulnerabilities relating to the JSON parser at the Rails end. That has really drowned out any google search relating to this specific XSS issue.
There are a couple of approaches to resolving this:
Have your application only respond to put/post/delete requests. That's not really an option when integrating to Angular - well, it is, but it means overriding a bunch of standard behaviour
Insert something at the front of your returned JSON - this can be the root node (default rails behaviour in rails 3, no longer in 3.1), a closure like )]};, or a loop like while (1);. Angular expects and can deal with )]}',
I've looked at using a json template in my rails app. You can do this with one of many gems, the one I like the look of is JBuilder (railscast 320), but RABL is perhaps more powerful (railscast 322).
This does mean a template for each of the actions on each of the controllers. However, I've also just completed working out how to have rails scaffold those for me automatically, so it's not as scary as it was when I first asked the question, and I can see some other reasons that I might want more control over the json that is returned from my application.
Having said that, I couldn't immediately see a way to get JBuilder to prepend an arbitrary string - it seems to only want to prepare valid JSON (and this I think is not valid JSON). RABL looks like it can do it, but it is a bit more complex. It can definitely be done through just using ERB, but I feel kinda wrong in doing that.
The other alternative I've identified is a helper method in application_controller.rb, which I then call in each of my controller methods. This is reasonably elegant, and I can quite easily change my template to do it. So I'm going with this for now:
class ApplicationController < ActionController::Base
def render_with_protection(json_content, parameters = {})
render parameters.merge(content_type: 'application/json', text: ")]}',\n" + json_content)
end
end
class ClubsController < ApplicationController
respond_to :json
# GET /clubs.json
def index
#clubs = Club.all
render_with_protection #clubs.to_json
end
# GET /clubs/1.json
def show
#club = Club.find(params[:id])
render_with_protection #club.to_json
end
# POST /clubs.json
def create
#club = Club.new(params[:club])
if #club.save
render_with_protection #club.to_json, {status: :created, location: #club}
else
render_with_protection #club.errors.to_json, {status: :unprocessable_entity}
end
end
end
Note that you should be also including CSRF protection in your application controller - so see this as additive to the security precautions you were already taking, not a replacement.

Should I use a view to render complex json data where I need view helpers?

I'm thinking about this since a while: I'm build a single-page webapp with ExtJS library.
The whole GUI will be handled with JSON through AJAX requests, so I effectively never use the view functionality (except for the main page, where the view is six lines just to write basic tags).
However, I struck in a problem which I think will happen often during my application: I have a complex json which needs a lot of view helpers to be built, expecially for referencing other resources like images. These json objects are statically written by me, for example I need to configure ext to render some desktop-like icons which needs title and a reference to an image. I think this should be written as a view where params are an array of hashes containing title/image.
That being said, I were thinking about an approach where my json objects will be built by the view, and not by the controller as I'm currently doing.
My questions is:
Is this approach OK? I feel like violating MVC pattern when I try to use image_path helper inside my controller.
It's important to understand that I'm not trying to fetch something in the view, I'm just passing some model objects as params to views and write them in a json fashion. I can't (always) use .to_json method because sometimes I need to organize those json objects in a totally different way.
Edit 1:
I'm adding a small question that could become really useful with this approach: is possible to parse (in the controller) a YML.ERB file in the controller but allowing it (the yml) to use all those nice helpers that I have in html.erb files? I would like to use them because is nicer to build some static json objects in yml rather than plain json. If that's the case, how to do it? Remember that I'm in a controller. Thanks a lot.
You can easily write json directly in your view templates, just like you would be writing erb, or xml. E.g. if you have a controller like this
class FoosController < ApplicationController
def index
end
end
And you accept format in your routes, then you can simply create a view
<% # app/views/foos/index.json.erb %>
{ some_json_stuff: "<%= link_to 'home', root_url %>" }
This view will be rendered when you access /foos.json. This enables you to write any custom json with helpers, partials, etc.
You might want to check out Draper, which is a gem that provides view models for Rails. It is ideal for things like JSON views, which arguably have no place in the model.
It also allows you access to helpers and the request context.
class ArticleDecorator < ApplicationDecorator
decorates :article
def as_json(optsions={})
{
:id => model.id,
:foo => helpers.some_helper_method,
:secret => helpers.current_user.admin? ? "secrets!" : "no secrets"
# ...
}
end
end

Resources