render layout stored in database - ruby-on-rails

Doing some integration work with another site I've got the unusual requirement of needing to create the layout at runtime.
At the moment I'm having to resort to something like this:
def new
body = render_to_string 'new', :layout => false
page = add_layout(body, db.load_template)
render :text => page
end
This is a bit awkward, I'd rather do something like:
def new
...
render 'new', :layout => db.load_template
end
Is there a cleaner way to do this? Perhaps it's possible to register new layouts at runtime and use the normal syntax?

Ha! I encountered a project that will solve just that. Check out panoramic. It stores rails views in the database instead of the filesystem.

You can extend ActionController::Base (or ApplicationController) with a module and alias_method_chain to make this work.
module Foo
alias_method_chain :render, :dblayout
def render_with_dblayout options = nil, extra_options = {}, &block
if options.include? :dblayout
...
else
render_without_dblayout options, extra_options { yield }
end
end
end
ActionController::Base.send(:include, Foo)

Related

Rails 4 - How to render JSON regardless of requested format?

I'd like a Rails controller (all of them, actually, it's an API) to render JSON always always.
I don't want Rails to return "route not found", or try and fail to find an HTML template, or return 406. I just want it to automatically and always render JSON, e.g. from a RABL or JBuilder view.
Is this possible? Related questions seem to have answers that have the aforementioned downsides.
You can add a before_filter in your controller to set the request format to json:
# app/controllers/foos_controller.rb
before_action :set_default_response_format
protected
def set_default_response_format
request.format = :json
end
This will set all response format to json. If you want to allow other formats, you could check for the presence of format parameter when setting request.format, for e.g:
def set_default_response_format
request.format = :json unless params[:format]
end
You can use format.any:
def action
respond_to do |format|
format.any { render json: your_json, content_type: 'application/json' }
end
end
It's just:
render formats: :json
I had similar issue but with '.js' extension. To solve I did the following in the view:
<%= params.except!(:format) %>
<%= will_paginate #posts %>
I tried the above solutions and it didn't solve my use case.
In some of the controllers of my Rails 4.2 app, there was no explicit render called. For example, a service object was called and nothing was returned. Since they are json api controllers, rails was complaining with a missing template error. To resolve I added this to our base controller.
def render(*args)
options = args.first
options.present? ? super : super(json: {}, status: :ok)
end
It's a large app I'm converting to Rails 5, so this is just a safety measure as I removed the RocketPants gem that seemed to do this automatically.
As a note, my controllers inherit from ActionController::Base
Of course:
before_filter :always_json
protected
def always_json
params[:format] = "json"
end
You should probably put this in a root controller for your API.

Access a controller's instance variable from a block using instance_eval

I'm making a breadcrumb module for my Ruby on Rails application, but I wanted a specific syntax - which I thought was good looking and more intuitive for Rails developers.
Here's the deal:
class WelcomeController < ApplicationController
breadcrumb_for :index, :text => 'Home', :href => -> { root_path }
def index
end
end
See, it's neat.
You can safely ignore the everything else but that proc - what I assign to the :href key.
I use instance_eval so that when the proc is evaluated it has access to the root_path helper.
And it worked. The example above is okay. BUT then I wanted to use an instance variable and that didn't work.
Like this:
class WelcomeController < ApplicationController
breadcrumb_for :index, :text => 'Home', :href => -> { #path }
def index
#path = root_path
end
end
Now, in that proc context #path is nil.
What should I do so I can access the instance variables from the block ?
Below is all the code of my module. Note that when I "process" the blocks and use instance_eval (aka call my module's #breadcrumb) the action should already be evaluated so the instance variable #path should already exist.
module Breadcrumb
extend ActiveSupport::Concern
included do
cattr_accessor(:_breadcrumb) { [] }
helper_method :breadcrumb
def self.breadcrumb_for(*args)
options = args.pop
_breadcrumb.push([args, options])
end
end
def breadcrumb
#breadcrumb ||= self._breadcrumb.map do |item|
puts item
if item[0].include?(params[:action]) || item[0][0] == '*'
text, href = item[1].values_at(:text, :href)
if text.respond_to?(:call)
text = instance_eval(&text)
end
if href.respond_to?(:call)
href = instance_eval(&href)
end
[text, href]
end
end
end
end
Oh no. I'm ashamed to say but it was my mistake. The code above works just fine, I was using different variable names in my application, not shown in the excerpt I used in the question.
Thanks anyway, I'll left it here for reference.

Rails ActiveRecord relations - avoiding writing .blank? checks

This is more a style question than anything.
When writing queries, I always find myself checking if the result of the query is blank, and it seems - I dunno, overly verbose or wrong in some way.
EX.
def some_action
#product = Product.where(:name => params[:name]).first
end
if there is no product with the name = params[:name], I get a nil value that breaks things.
I've taken to then writing something like this
def some_action
product = Product.where(:name -> params[:name])
#product = product if !product.blank?
end
Is there a more succinct way of handling nil and blank values? This becomes more of a headache when things rely on other relationships
EX.
def some_action
#order = Order.where(:id => params[:id]).first
# if order doesn't exist, I get a nil value, and I'll get an error in my app
if !#order.nil?
#products_on_sale = #order.products.where(:on_sale => true).all
end
end
Basically, is there something I haven;t yet learned that makes dealing with nil, blank and potentially view breaking instance variables more efficient?
Thanks
If its just style related, I'd look at Rails' Object#try method or perhaps consider something like andand.
Using your example, try:
def some_action
#order = Order.where(:id => params[:id]).first
#products_on_sale = #order.try(:where, {:onsale => true}).try(:all)
end
or using andand:
def some_action
#order = Order.where(:id => params[:id]).first
#products_on_sale = #order.andand.where(:onsale => true).andand.all
end
Well even if you go around "nil breaking things" in your controller, you'll still have that issue in your views. It is much easier to have one if statement in your controller and redirect view to "not found" page rather than having several ifs in your views.
Alternatively you could add this
protected
def rescue_not_found
render :template => 'application/not_found', :status => :not_found
end
to your application_controller. See more here: https://ariejan.net/2011/10/14/rails-3-customized-exception-handling

Rails: how do I use a template from somewhere other than the file system?

I have an application that needs to support a small set of trusted users uploading new templates. I'll store them in the database or in S3. My question is: how do I tell the controller to render a given template? Of course, I could do it with a manual ERB call:
class MyController < ApplicationController
def foo
template_source = find_template(params[:name])
template = Erubis::Eruby.new(template_source)
render :text => template.result({ :some => #data })
end
end
But then I lose things like helpers and the automatic copying of instance variables.
You could do it using render :inline
render :inline => find_template(params[:name])

refactor "render format" in multiple controllers

so i've got a view method in multiple controllers which mostly looks exactly the same:
def show
show! do |format|
format.json do
if #text.activated?
#text.log
render_for_api :texts_all, :json => #text
else
render :nothing => true
end
end
format.pdf do
pdf = QrPdf.new(#text)
send_data pdf.render, filename: "text_#{#text.id}.pdf", type: "application/pdf"
end
end
end
the models for this are different, but they all have the same attributes that are used in this method (activated, log, id). i also could change the render_for_api given hash from which is currently texts_all, documents_all etc to a hash that its everywhere the same.
is there a way to use this code in multiple models without having this enormous duplication?
i'm thankful for every hint!
especially i find it hard to deal with the do |format| block. but also i'm not sure where to put the code and how to use it with different types of models.
thank you.
If the model is truly generic:
def show
show_model #text
end
I'm not sure what show! is, but that part you can figure out. Roughly (untested):
def show_model(obj)
show! do |f|
f.json do
return render(:nothing => true) unless obj.activated?
obj.log
render_for_api :texts_all, :json => obj
end
f.pdf do
opts = { filename: "text_#{obj.id}.pdf", type: "application/pdf" }
send_data QrPdf.new(obj).render, opts
end
end
end
As far as where show_model lives, I tend to put things like that into a base controller, or as a mixin, but there may be better options. Since I usually have a base controller, it's just easy to keep it there.

Resources