I have a rails 3.2 application that recently I added mobile-fu gem to, in order to add separate mobile views.
There were a few hiccups but, for the most part, it works wonderfully.
However, I've only made mobile views for a handful of pages. When I attempt to go to a page that does not have a mobile view, from a mobile device, I get:
Missing template after_hour_it_supports/index, application/index with {:locale=>[:en], :formats=>[:mobile], :handlers=>[:erb, :builder, :prawn, :prawn_dsl]}
Which is what I would expect.
However, it is necessary to display some pages which might never have mobile counterparts. Specifically, there is a page to approve something. The thing that they are approving might never have a mobile view made for it, but the approval page already has a mobile view. My current approach, is to use an iframe to display the possibly non-mobile view. Of course, if a mobile view exists, I would prefer to use it over the non-mobile view (still in the iframe).
So what I would like to do is to attempt to render template with the :mobile format, but if the mobile format does not exist, to render with the :html format, which seems like something rails already does based on the :formats array mentioned in the MissingTemplate exception. I can find some documentation on how to set the :formats array when calling render, but I would like to do this automatically, without having to modify every existing response.
How do I modify :formats=>[:mobile] to be :formats=>[:mobile, :html] on an application level?
I finally found an answer which seems to work for me (the answer by Will Madden).
Specifically, I used his suggestion to override the formats= in my ApplicationController. Which he says is the same way that rails already adds this exact same functionality for the :js format.
The specific method he writes in his answer looks like this:
class ApplicationController
...
def formats=(values)
values << :html if values == [:mobile]
super(values)
end
...
end
This is by far the most elegant solution I have found for my circumstances. However, it fails whenever the controller for the page in question contains a respond_to block for the current action.
Related
I am using rack-mini-profiler in my rails 3.2 project.
In gemfile:
gem 'rack-mini-profiler'
Everything works great. But my application is mostly a set of json endpoints. So while it is very useful to be to able to inspect the performance of html pages, I would like to also be able to see the performance of controllers that return json.
Example of my controller:
class UsersController < BaseController
def json_method
# you don't see the mini profiler ui for this controller
render json: { users: [:foo, :bar]}
end
end
If I go to localhost:3000/users/json_method, I see my json response but not the profiler ui.
In development, by default, the rack-mini-profiler gem collects the previous JSON call and presents it in the menu accessible from the HTML page. No code change required.
So, make your JSON request, then hit any other HTML page and it will be available in the list. We use this to great effect. If your Rails app is a JSON API service only, make sure you have a 404.html in your public folder at least, then hit something like:
http://localhost/404.html
In the second tab, you can visit this URL /rack-mini-profiler/requests Where you can see the last request log
if rack mini profiler can’t display the results, it will collect them until it can on the next HTML page. So, the solution that I am using is to:
make the JSON request and
then hit an HTML page of my choice.
The results will appear, along with the most recent HTML profile.
http://tech.eshaiju.in/blog/2016/02/25/profile-api-endpoints-using-rack-mini-profiler/
As a first solution, you can just set the format to html, and render inside the html page:
The controller:
class UsersController < BaseController
def json_method
#users_json { users: [:foo, :bar]}
render 'index', formats: ['html']
end
end
And in app/views/users/index.html.erb:
Users:<br/>
<%= #json.inspect %>
I don't care so much about my json result, now I have the profiling ui.
A solution where I have the profiling ui without changing my controller would be much better.
Note that in a new Rails API project initialized using rails new api_name --api, the ApplicationController inherits from ActionController::API, instead of ActionAcontroller::Base. In this case, mini-profiler might not load when your HTML page is shown.
I had to change the base class to ActionController::Base to make it work. If in your app you see no requests to load resources from mini-profiler on your HTML page, you may want to try this change. Took me a long while to figure out.
Also note that you do need to have at least the <body> tag in your template to be rendered, otherwise the mini-profiler divs will not be properly injected.
Based on several tutorials online, my rails app uses a :mobile format to render pages optimized for mobile devices.
So for some actions, I have a *.mobile.erb file, in addition to the *.html.erb file.
But what if I want to disallow certain actions on the mobile site?
For example, for a particular resource, you can do Index and Show, but not Edit or New, when you're mobile.
What's the best way to do this? Currently, I just don't have edit.mobile.erb or new.mobile.erb, but that just shows a "Template Missing" error - not very graceful.
Any suggestions?
You could try accessing params[:format] to check if the action is valid for a particular format. If not, you could redirect or show a custom error page, or whatever else it is you want to do to gracefully disallow an action.
#..inside your controller..
def do_something
if !["html","xml"].include?(params[:format])
#invalid format. do something
end
#..do other things
end
In my current project I have a couple instances where I have a re-usable form that exists inside a rails partial. This form submits to a specific controller via ajax (:remote => true). The controller does some stuff and then returns back the appropriate js.erb to modify the page via javascript.
This works fine for when I have a single view. But the problem seems to happen when this re-usable partial exists on multiple views. In view 1 I might want to issue a completely different set of javascript commands then in view 2.
As a concrete example, say I have a comments controller that has the normal CRUD operations.
I now have partial called _comments_box.erb. This _comments_box.erb contains the ability to submit a comment via a simple line:
- form_for comment, :url => post_comments_path(post), :remote => true do |f|
This submits to a comments_controller.rb create method which looks somethings like this:
def create
... do some stuff, like create a new comments model
respond_to do |format|
# will respond with create.js.erb
format.js
end
end
The create.js.erb in turn adds a comment to the view, perhaps doing a bunch of other updates to the DOM.
Say I render the _comments_box.erb within a view called post_summary.erb. Now I have another view, post_detail.erb that requires the same _comments_box.erb. However the post_detail.erb requires me to update completely different divs on the DOM in response to a new comment.
I need to create a different JS response for each instantiation. So I can either:
Create an alternate controller method, say create_2. Pass in some parameter to the _comments_box.erb from post_detail.erb to the _comments_box.erb partial so it knows which controller method to fire. This will allow me to have a separate file _create_2.js.erb that will allow me to manipulate the post_detail.erb view independently.
Forget about using js.erb altogether and just use plain old AJAX and get back JSON, and handle the javascript manipulation completely on the client-side.
It seems option 1 allows me to continue to use the UJS supported by Rails which is nice. But also means I probably will be adding a lot of duplicate code everywhere which is annoying. Is there a way for me to do this elegantly while continuing to use UJS?
That's exactly the purpose of Apotomo: http://apotomo.de/
Here is it's own description:
Apotomo is a true MVC widget framework
for Rails. Widgets are based on Cells
and provide reuseable view components.
Having bubbling events, they know when
and how to update themselves via AJAX!
Working with Apotomo widgets almost
feels like developing GUI components –
in a Rails environment.
Have a try, it's great.
I'd not recommend using UJS for frontend apps: server shouldn't take care of client side business. I agree it's useful and clean but it lacks performance and thus should be kept for backend stuff (RJS will move into a gem, see here: http://weblog.rubyonrails.org/2011/4/21/jquery-new-default).
That said, back to the solutions you expose:
1) I think you won't need an extra controller, you'd just have to pass additional params in order to know from where to query came from. A hidden_field could do the trick. With this info, render the good js.erb file
format.js { if condition
render "create.js.erb"
else
render "create_2.js.erb"
end
}
2) I'd go for it and return json but you'll face the same problem: knowing from where the request comes from.
A better solution (than using a hidden_field) might be to check the request.referer in your controller action. This way you leverage the fact that each context has a unique URL, and don't have to explicitly specify another unique value when rendering your widget partial.
I'm trying to setup an ajax-based website that loads a fragment of a webpage when a specific combination of GET variables and HTTP Headers are given.
GET /normal/html/page?ajax=true
X-ajax: true
What I've setup in my controller is:
before_filter do |controller|
if request_by_ajax?
ApplicationController.layout false
end
end
This works, but only in development mode. You see when I attempt to access the given page it only gives the fragmented (layout free) page.
When the normal page is accessed without ajax:
GET /normal/html/page
This returns only the view for that page and NOT the layout around it. But if I access that page when the webserver is reloaded then it returns the layout and when its accessed afterwards via AJAX it returns the layout + the view. Clearly there is a caching issue here.
I would really like to keep the same route for the page. If there is an ajax call then I would prefer to figure out the response based on the querystring and request header values. But rails prefers to classify querystring parameters and request headers as meaningless when serving a file (i.e. /normal/html/page and /normal/html/page?ajax=true) return the same actual template file (this what I assume).
Any idea how to get around this?
I figured out the issue.
It turns out that the layout call must be defined early on in the application controller:
---- application_controller.rb
layout :choose_layout
def choose_layout
if request_by_ajax?
false
else
'application'
end
end
Works like a charm.
My controller has two methods: index and search.
def index: renders the default view, index.html.erb, and displays a default map using YM4R/GM.
def search: renders the default view using render index, but uses form input to display a custom map using YM4R/GM.
The view, index.html.erb, contains a partial view, _form.html.erb, and the #map.div for YM4R/GM.
The map displays fine when rendered through the default, but when submitted to the search method I get the error:Template is missing. Missing template ym4r/gm_plugin/variables/_variable.erb in view path app/views.
What's weird is that I had this working yesterday until I started jacking around with trying to make my form submit using form_remote_tag. I started getting this error with that type of call so now I've changed everything back (I think), but I'm still getting the error. I think there's something I don't understand about how Rails renders views.
As you've pointed out here, there's an important difference between a method and a string.
# Call the index method, then render the result, whatever that is. Does not work as
# expected.
render(:action => index)
# Render using the index template
render(:action => 'index')
Ruby is really relaxed when it comes to calling methods so there's no requirement to use brackets as is the case with others such as JavaScript or Perl.