class ProductsController < ApplicationController
layout :products_layout
def show
#product = Product.find(params[:id])
end
private
def products_layout
#current_user.special? ? "special" : "products"
end
end
Here when is method products_layout getting executed? There's nowhere I can see that calls the method products_layout so how could the symbol :products_layout be defined?
Rails has implicit rendering, so at the end of the method "show", since you haven't told Rails to do anything different, it will render the app/views/products/show.html.erb template.
In addition, it will look to see what layout you specified. here, you've given a symbol to layout, which Rails takes to mean "execute this method name to find out what layout I should use"
Related
I have a controller: "/app/controllers/analyst/test_orders_controller.rb".
In this file I have:
class Analyst::TestOrdersController < ApplicationController
def new
#order = Order.new
end
end
But I have an error:
uninitialized constant Analyst::TestOrdersController::Order
But I don't want to use Analyst::TestOrdersController::Order.new, I just want to use Order.new. It is strange. What is the problem?
Use ::Order.new
The interpreter is looking for the definition of Order under the Analyst module namespace, this happens because the application models are loaded lazily so the file models/order.rb has not been read yet. Adding the general namespace tells it search for the definition in the Rails paths.
The way to confirm this is to add some random function call in the Order model body and see that it's not executed unless you call ::Order explicitly.
try:
module Analyst
class TestOrdersController < ApplicationController
def new
#order = Order.new
end
end
end
I think it has to do with module nesting:
https://cirw.in/blog/constant-lookup
I'm serving a versioned web service from Rails.
I would very much like to be able to call render like normal:
render 'index'
And have it correctly serve the requested version from the following:
index.v1.json.jbuilder
index.v2.json.jbuilder
index.v3.json.jbuilder
Assuming that I already know the requested version within the context of the controller action execution, how do I get render() to leverage it?
I have used the versioncake gem
You should definitely check this out. File name will be very close to what you have:
index.v1.json.jbuilder
would be
index.json.v1.jbuilder
Sounds like a builder design pattern might work here. Have a view builder object that returns the desired behavior.
module ViewBuiler
def build(name, api_version)
View.new(name, api_version).build
end
class View < Struct(:name, :api_version)
def build
[name, api_version, 'json', 'jbuilder'].join('.')
end
end
end
and in your controller you could just do something like:
ApplicationController
include ViewBuilder
end
MyController < ApplicationController
def index
...
# you can pass either strings or symbols to build and it will
# return 'index.v1.json.jbuilder'
render build(:index, params[:api_version])
end
end
And disclaimer, this is just an idea and not something I've implemented. I like the approach for 2 reason. The Controller actions remain skinny and don't have logic in it. A builder like this seems pretty easy to test. And it keeps the thing that might change (views etc) isolated into something that can change frequently as long as it retains it's interface that the Controllers will work with.
This seems like a candidate for Variants. This is new to 4.1 though.
class MyController < ActionController::Base
before_action :set_variant
def my_action
.....
respond_to do |format|
format.json do |json|
json.v1 do
# render whatever you need here
end
end
end
end
protected
def set_variant
request.variant = :v1 if request.params[:version] == "v1"
....
end
end
I am working on a cms, and have the need for dynamic template choice in the pages controller. I have a before filter that grabs the chosen template name in the user settings. Now I need to figure out how to render the correct layout using that instance variable.
Here is what I have so far:
#This sets #template to the template object. #template.name is "Default"
before_filter :get_template
layout "templates/#{#template.name.downcase.gsub(" ", "_")}"
#layout "templates/default" #This line renders fine
I'm getting the following error:
undefined method `name' for nil:NilClass
My guess is that the before_filter doesn't necessarily run 'before' the template is called.
Is there a better way that I should be trying to accomplish this? I do not really have experience in using many templates and choosing which one to render.
Thanks in advance!
Try this:
class PagesController < ApplicationController
def template_path
#... returns the template path, e.g. "layouts/theme_a"
end
def set_template
self.class.layout(template_path)
end
before_filter :set_template
end
get_template inside of application controller , so we can access from any controlller :
--------------------
class ApplicationController < ActionController::Base
#template=get_template
layout "templates/#{#template.name.downcase.gsub(" ", "_")}"
end
This should work
class ApplicationController < ActionController::Base
layout :set_custom_layout
def set_custom_layout
get_template
end
end
My question is about controller methods (possibly included from an outside class) that work with instance variables. I frequently use a before_filter in controllers to set up certain variables, e.g.:
class DocumentController < ApplicationController
before_filter :fetch_document
def action
#document.do_something
end
private
def fetch_document
#document = Document.find(params[:id])
end
end
I've been working on a project in which a few controllers will share some functionality, say, document editing. My first thought was to extract the relevant methods, and get them from application_controller.rb or a separate module. But then I noticed I was writing code that looks like this:
def fetch_document
#document = Document.find(params[:id])
end
def do_something_to_document
#document.do_something
end
This sets off warning bells: do_something_to_document is essentially assuming the existence of #document, rather than taking it as an argument. Is this, in your sage opinions, a bad coding practice? Or am I being paranoid?
Assuming it is an issue, I see two general approaches to deal with it:
Check for the instance var and bail unless it's set:
def do_something_to_document
raise "no doc!" unless #document
[...]
end
Call the action with the instance var as an argument:
def do_something_to_document(document)
[...]
end
2 looks better, because it hides the context of the calling object. But do_something_to_doc will only be called by controllers that have already set up #document, and taking #document as a method argument incurs the overhead of object creation. (Right?) 1 seems hackish, but should cover all of the cases.
I'm inclined to go with 1 (assuming I'm right about the performance issue), even though seeing a list of methods referencing mysterious instance vars gives me hives. Thoughts? Let me know if I can be more clear. (And of course, if this is answered somewhere I didn't see it, just point me in the right direction...)
Thanks,
-Erik
If you really need document in different controllers, I'd do something like this:
class ApplicationController < ActionController::Base
private
def document
#document ||= Document.find(params[:document_id])
end
end
class FooController < ApplicationController
before_filter :ensure_document, :only => [:foo]
def foo
document.do_something
end
private
# TODO: not sure if controller_name/action_name still exists
def ensure_document
raise "#{controller_name}##{action_name} needs a document" unless document
end
end
As #variable are session/instance variable you will get a Nil exception in do_something_to_document method.
The first code is fine, because before_filter will always load your #document.
I suggest you to write something like that
def fetch_document(doc_id)
#document ||= Document.find(doc_id)
end
def do_something_to_document
my_doc = fetch_document(params[:id])
end
where do_something_to_document is in the controller (if not, dont use params[:id], even if you know you can access this global, use another explicit parameter). The ||= thing, will asssure that you call the base only once by request.
In one of the controller, I need a specific layout. I added layout at the beginning. It works well.
But if I add an initialize function for some controller-based variable. Rails seems just ignore the layout command.
Is anyone have same problem? How can I fix it?
class AdminsController < ApplicationController
layout "layout_admins"
def initialize
#Title = "Admins"
end
def index
....... some code here
end
end
initialize is used internally to Rails to, well, initialize a new instance of your controller so it can then serve requests on it. By defining this method in this particular manner, you are breaking Rails.
There is a way through! A light at the end of the tunnel. A pot of gold at the end of the rainbow:
def initialize
#title = "Admins"
super
end
See that little super call there? That'll call the superclass's initialize method, doing exactly what Rails would do otherwise. Now that we've covered how to do it your way, let's cover how to do it the "officially sanctioned" Rails way:
class AdminsController < ApplicationController
before_filter :set_title
# your actions go here
private
def set_title
#title = "Title"
end
end
Yes, it's a little more code but it'll result in less frustration by others who gaze upon your code. This is the conventional way of doing it and I strongly encourage following conventions rather than doing "magic".
EDIT: If you're using Rails 5 then you'll need to use before_action instead of before_filter.
I'm not sure exactly how layout works its magic, but I'm willing to bet it's in a yield block in the ActionController#initialize method. So your overriding of initialize would explain the problem.
Looks like you have too options here:
Close out your new definition with super to call the ActionController initialize which should use the layout defined in the class.
eg:
def initialize
#Title = "Admins"
super
end
Use a before filter to initialize your variables. This is the Rails Way of initializing values in a controller
class AdminsController < ApplicationController
layout "layout_admins"
before_filter :set_title
def set_title
#Title = "Admins"
end
def index
....... some code here
end
end