New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format fallback #3855

Closed
Dorian opened this Issue Dec 5, 2011 · 31 comments

Comments

Projects
None yet
@Dorian
Contributor

Dorian commented Dec 5, 2011

The situation

I have html views, like index.html.erb, and it is the normal view of my website.
I also have mobile views (format = :mobile), like index.mobile.erb.
format = :mobile is automaticaly set when a mobile device watch my website

The issue

When an user see posts#show from his mobile, I did not write a mobile view for this action, so he have an error.
Instead, he should see the mobile layout (layouts/application.mobile.erb) with the posts/show.html.erb in it.

How to resolve this

I don't know a lot about Rails source code, but people from StackOverflow tried to solve this issue with hacks of ActionView::FileSystemResolver and ActionView::PathResolver classes, there are some possibles solutions, but I don't understand them quite well so I will not integrate it in my website.

Please, people of the rails community, can somenone solve this ?

@Dorian

This comment has been minimized.

Show comment
Hide comment
@Dorian

Dorian Dec 6, 2011

Contributor

Or we could do request.format = [:mobile, :html], it :mobile is not found, we try with :html.

This could be very useful.

Contributor

Dorian commented Dec 6, 2011

Or we could do request.format = [:mobile, :html], it :mobile is not found, we try with :html.

This could be very useful.

@Dorian

This comment has been minimized.

Show comment
Hide comment
@Dorian

Dorian Dec 7, 2011

Contributor

I've done a Monkey Patch on ActionView::PathResolver#query. Damn there is no public API...

Contributor

Dorian commented Dec 7, 2011

I've done a Monkey Patch on ActionView::PathResolver#query. Damn there is no public API...

@holli

This comment has been minimized.

Show comment
Hide comment
@holli

holli Jan 27, 2012

Contributor

I have a similar issue. That monkey patch seems "good" way although I didn't try it.

In case someone else wants a simpler monkey patch, they can add following to all .html.erb pages that need to render mobile or html layout depending on the request. This is not so dry approach but simple if you have only couple of files.

In the beginning of posts/show.html.erb

<% self.formats = [:mobile, :html] if mobile_site? %>

For someone who wonders about why this is and maybe how to fix it:

This is a bit of quessing :) :

View partials/pages get only one of the formats passed to them. So if you try "<% puts(self.formats) %>" in the view its not the same as in controller "puts self.formats". In .html.erb-view its [:html] while in controller it can be [:mobile, :html, :css].

Because action/page/views are rendered before layouts the final self.formats has to be assigned insiden erb to find the right layout.

Contributor

holli commented Jan 27, 2012

I have a similar issue. That monkey patch seems "good" way although I didn't try it.

In case someone else wants a simpler monkey patch, they can add following to all .html.erb pages that need to render mobile or html layout depending on the request. This is not so dry approach but simple if you have only couple of files.

In the beginning of posts/show.html.erb

<% self.formats = [:mobile, :html] if mobile_site? %>

For someone who wonders about why this is and maybe how to fix it:

This is a bit of quessing :) :

View partials/pages get only one of the formats passed to them. So if you try "<% puts(self.formats) %>" in the view its not the same as in controller "puts self.formats". In .html.erb-view its [:html] while in controller it can be [:mobile, :html, :css].

Because action/page/views are rendered before layouts the final self.formats has to be assigned insiden erb to find the right layout.

@phoet

This comment has been minimized.

Show comment
Hide comment
@phoet

phoet Mar 6, 2012

Contributor

this issue is really annoying if you have a lot of pages relying on templates in different formats...

Contributor

phoet commented Mar 6, 2012

this issue is really annoying if you have a lot of pages relying on templates in different formats...

@Dorian

This comment has been minimized.

Show comment
Hide comment
@Dorian

Dorian Mar 6, 2012

Contributor

For information, I made it a different way because I didn't want to maintain this monkey patch :
w
I made a layout? function who decide if we are in a mobile context or not, and I change the layout (with layout :choose_layout and def choose_layout ...) so I have a mobile.html.erb layout with it's own CSS.

Contributor

Dorian commented Mar 6, 2012

For information, I made it a different way because I didn't want to maintain this monkey patch :
w
I made a layout? function who decide if we are in a mobile context or not, and I change the layout (with layout :choose_layout and def choose_layout ...) so I have a mobile.html.erb layout with it's own CSS.

@carlosantoniodasilva

This comment has been minimized.

Show comment
Hide comment
@carlosantoniodasilva

carlosantoniodasilva Mar 30, 2012

Member

Can someone please confirm if this issue still exists with latest 3.2.2 or 3.2.3.rc2? There have been some changes on format handling in these versions, just want to check the issue still exists. Thanks.

Member

carlosantoniodasilva commented Mar 30, 2012

Can someone please confirm if this issue still exists with latest 3.2.2 or 3.2.3.rc2? There have been some changes on format handling in these versions, just want to check the issue still exists. Thanks.

@jdelStrother

This comment has been minimized.

Show comment
Hide comment
@jdelStrother

jdelStrother Apr 9, 2012

Contributor

Yes, it's still an issue. request.formats=[:mobile, :html] works fine for the top level template, but once inside, say, 'posts.mobile.erb', the format seems locked to mobile, so it won't fall back to html if you try to render a partial without a corresponding html version.

Contributor

jdelStrother commented Apr 9, 2012

Yes, it's still an issue. request.formats=[:mobile, :html] works fine for the top level template, but once inside, say, 'posts.mobile.erb', the format seems locked to mobile, so it won't fall back to html if you try to render a partial without a corresponding html version.

@jdelStrother

This comment has been minimized.

Show comment
Hide comment
@jdelStrother

jdelStrother Apr 9, 2012

Contributor

@Dorian do you still have a working monkey patch for rails 3.2? Your original link is 404ing.

Contributor

jdelStrother commented Apr 9, 2012

@Dorian do you still have a working monkey patch for rails 3.2? Your original link is 404ing.

@Dorian

This comment has been minimized.

Show comment
Hide comment
@Dorian

Dorian Apr 9, 2012

Contributor

Here it is (but it may not work) : https://gist.github.com/2343873

I prefer using a "mobile" layout (with mobile.css) instead. So you don't have to rewrite all your views.

Contributor

Dorian commented Apr 9, 2012

Here it is (but it may not work) : https://gist.github.com/2343873

I prefer using a "mobile" layout (with mobile.css) instead. So you don't have to rewrite all your views.

@jdelStrother

This comment has been minimized.

Show comment
Hide comment
@jdelStrother

jdelStrother Apr 9, 2012

Contributor

Thanks for that. I've been playing around a bit more, and came up with this :

class ApplicationController < ActionController::Base
  class MobileFallbackResolver < ::ActionView::FileSystemResolver
    def find_templates(name, prefix, partial, details)
      if details[:formats] == [:mobile]
        # Add a fallback for html, for the case where, eg, 'index.html.haml' exists,
        # but not 'index.mobile.haml'
        details = details.dup
        details[:formats] = [:mobile, :html]
      end
      super
    end
  end
  append_view_path MobileFallbackResolver.new('app/views')
end

which actually seems to work pretty well for our needs.

Contributor

jdelStrother commented Apr 9, 2012

Thanks for that. I've been playing around a bit more, and came up with this :

class ApplicationController < ActionController::Base
  class MobileFallbackResolver < ::ActionView::FileSystemResolver
    def find_templates(name, prefix, partial, details)
      if details[:formats] == [:mobile]
        # Add a fallback for html, for the case where, eg, 'index.html.haml' exists,
        # but not 'index.mobile.haml'
        details = details.dup
        details[:formats] = [:mobile, :html]
      end
      super
    end
  end
  append_view_path MobileFallbackResolver.new('app/views')
end

which actually seems to work pretty well for our needs.

@carlosantoniodasilva

This comment has been minimized.

Show comment
Hide comment
@carlosantoniodasilva

This comment has been minimized.

Show comment
Hide comment
@carlosantoniodasilva

carlosantoniodasilva Apr 10, 2012

Member

@Dorian @jdelStrother hey guys, I believe that if you have your views named .erb or .haml only, instead of .html.erb or .html.haml, it'll fallback to that view always, using it for both html and mobile formats, each picking their own layout.

I've tested here with the following structure:

app/views/
  layouts/
    application.html.erb
    application.mobile.erb
  posts/index.erb

When curling /posts, I get layout application.html + posts/index.erb rendered. When curling /posts.mobile, I get layout application.mobile + posts/index.erb. Is that correct?

Member

carlosantoniodasilva commented Apr 10, 2012

@Dorian @jdelStrother hey guys, I believe that if you have your views named .erb or .haml only, instead of .html.erb or .html.haml, it'll fallback to that view always, using it for both html and mobile formats, each picking their own layout.

I've tested here with the following structure:

app/views/
  layouts/
    application.html.erb
    application.mobile.erb
  posts/index.erb

When curling /posts, I get layout application.html + posts/index.erb rendered. When curling /posts.mobile, I get layout application.mobile + posts/index.erb. Is that correct?

@Dorian

This comment has been minimized.

Show comment
Hide comment
@Dorian

Dorian Apr 10, 2012

Contributor

@carlosantoniodasilva Yes this nicely solve this issue.

Contributor

Dorian commented Apr 10, 2012

@carlosantoniodasilva Yes this nicely solve this issue.

@phoet

This comment has been minimized.

Show comment
Hide comment
@phoet

phoet Apr 10, 2012

Contributor

yes, seems to work fine. i would recommend changing the deprecation warning that is produced when you use a render "template_name.html" to reflect the issues described here.

Contributor

phoet commented Apr 10, 2012

yes, seems to work fine. i would recommend changing the deprecation warning that is produced when you use a render "template_name.html" to reflect the issues described here.

@phoet

This comment has been minimized.

Show comment
Hide comment
@phoet

phoet Apr 10, 2012

Contributor

will be interesting to see how this works out when there are different templates for different types (like mobile and pdf) ...

Contributor

phoet commented Apr 10, 2012

will be interesting to see how this works out when there are different templates for different types (like mobile and pdf) ...

@carlosantoniodasilva

This comment has been minimized.

Show comment
Hide comment
@carlosantoniodasilva

carlosantoniodasilva Apr 10, 2012

Member

Great that it works.

@phoet I don't see how both are related. The template engine extension without format, ie .erb or .haml, will always be the fallback. Render with the .html format in the template/partial name is the one deprecated in favor of formats. Anyway, if you think the deprecation can be improved somehow, feel free to put up a pull request :).

I'm closing this issue for now, please let us know about any other issue you may have, thanks!

Member

carlosantoniodasilva commented Apr 10, 2012

Great that it works.

@phoet I don't see how both are related. The template engine extension without format, ie .erb or .haml, will always be the fallback. Render with the .html format in the template/partial name is the one deprecated in favor of formats. Anyway, if you think the deprecation can be improved somehow, feel free to put up a pull request :).

I'm closing this issue for now, please let us know about any other issue you may have, thanks!

@holli

This comment has been minimized.

Show comment
Hide comment
@holli

holli Apr 10, 2012

Contributor

@carlosantoniodasilva : Is this really solved/closed? If issue was about format fallbacks. Consider following:

In controller: request.formats=[:mobile, :html]

And then you have the following structure in app/views

  • layouts/
    • application.html.erb
    • application.mobile.erb
  • posts/
    • posts.html.erb
      • for full-html-version, uses post partial: <%= posts.each do |p| render :post, :locals => {:post => p} end %>
    • posts.mobile.erb
      • for full-mobile-version, uses post partial: <%= posts.each do |p| render :post, :locals => {:post => p} end %>
      • would want to use post.html.erb, as set in the controller formats
    • post.html.erb
    • post.json.erb etc other formats

You expect it to render mobile layout, posts.mobile.erb and automatically use post.html.erb because you set the request.formats=[:mobile,:html] in controller.

Using template engine extension without format will work but that's a bad choice if you have multiple extensions (json, xml, html, ...). You have two different html formats which can be used in most cases. The html is not fallback for all other formats.

In itself the issue is easy to go around if you know it but the fallback doesn't work as expected. Now I'm still needing the
<% self.formats = [:mobile, :html] if mobile_site? %> line in beginning of posts.mobile.erb to be able to render post.html.erb.

Contributor

holli commented Apr 10, 2012

@carlosantoniodasilva : Is this really solved/closed? If issue was about format fallbacks. Consider following:

In controller: request.formats=[:mobile, :html]

And then you have the following structure in app/views

  • layouts/
    • application.html.erb
    • application.mobile.erb
  • posts/
    • posts.html.erb
      • for full-html-version, uses post partial: <%= posts.each do |p| render :post, :locals => {:post => p} end %>
    • posts.mobile.erb
      • for full-mobile-version, uses post partial: <%= posts.each do |p| render :post, :locals => {:post => p} end %>
      • would want to use post.html.erb, as set in the controller formats
    • post.html.erb
    • post.json.erb etc other formats

You expect it to render mobile layout, posts.mobile.erb and automatically use post.html.erb because you set the request.formats=[:mobile,:html] in controller.

Using template engine extension without format will work but that's a bad choice if you have multiple extensions (json, xml, html, ...). You have two different html formats which can be used in most cases. The html is not fallback for all other formats.

In itself the issue is easy to go around if you know it but the fallback doesn't work as expected. Now I'm still needing the
<% self.formats = [:mobile, :html] if mobile_site? %> line in beginning of posts.mobile.erb to be able to render post.html.erb.

@carlosantoniodasilva

This comment has been minimized.

Show comment
Hide comment
@carlosantoniodasilva

carlosantoniodasilva Apr 11, 2012

Member

@holli I think there are two possibilities in this case:

  1. Use a _post.erb partial. Even if you have other formats, you're either having a specific template for them that'd be used instead, or you're using something like render :json, which won't use this partial.
  2. Continue using _post.html.erb partial, and give the :formats option when calling render :partial, to explicitly say you want to render the html format of that partial.

I think one of them would already solve the problem instead of elect html as the default fallback, unless I've missed something.

Member

carlosantoniodasilva commented Apr 11, 2012

@holli I think there are two possibilities in this case:

  1. Use a _post.erb partial. Even if you have other formats, you're either having a specific template for them that'd be used instead, or you're using something like render :json, which won't use this partial.
  2. Continue using _post.html.erb partial, and give the :formats option when calling render :partial, to explicitly say you want to render the html format of that partial.

I think one of them would already solve the problem instead of elect html as the default fallback, unless I've missed something.

@holli

This comment has been minimized.

Show comment
Hide comment
@holli

holli Apr 11, 2012

Contributor

Hmm, now when I look old controller code (with old rails) I was trying using something like request.formats = [Mime::Type.lookup(:mobile), Mime::Type.lookup(:html)]. I think it worked to some extend but was not really documented anywhere.

@carlosantoniodasilva suggestions:

  1. Works ok. It's just a bit ugly if you know the partial is fully html code. Why isn't html included in the filename then. I also want to avoid of defaulting some xml partials to html by accident.
  2. Wont work with more complicated cases. My path can go a lot further: posts.html.erb -> _post.mobile.erb -> _post_replys.html.erb -> _posting_user_photo.mobile.erb. Then in layout.erb there are selections between _menu.mobile.erb, _menu.html.erb and some other layout issues. There can be multiple places where you have to choose between mobile and html inside multiple partials. Hypothetically it can be even more complicated: e.g. setting self.formats=[:full_version,:html,:text_only]. Then you would have fallback with text_only version of partials.

Now I have line <% self.formats = [:mobile, :html] if mobile_site? %> at the beginning of each mobile view. Works quite well.

When I started implementing mobile I thought it would work in the same way as how locales/languages work with more complex fallbacks. I wasn't expecting to have html as default fallback but I was excepting to be able to choose the fallback for each format. E.g. with locales you could have fallback with setting I18n.fallbacks[:"de-CH"] = [:de, :en]etc.

I would like to do this format-setting in the controller or by defining mime type with extra settings. So that if I want to replace any of existing views/partials with mobile version I could do it simply by creating a new partial with different ending. That method was easy to explain non ruby mobile ui designer.

But yea. I think now everything works as documented :) With couple views it's easy to have the one line in the beginning. With many view/partial files it's easy to patch FileSystemResolver as commented previously.

Contributor

holli commented Apr 11, 2012

Hmm, now when I look old controller code (with old rails) I was trying using something like request.formats = [Mime::Type.lookup(:mobile), Mime::Type.lookup(:html)]. I think it worked to some extend but was not really documented anywhere.

@carlosantoniodasilva suggestions:

  1. Works ok. It's just a bit ugly if you know the partial is fully html code. Why isn't html included in the filename then. I also want to avoid of defaulting some xml partials to html by accident.
  2. Wont work with more complicated cases. My path can go a lot further: posts.html.erb -> _post.mobile.erb -> _post_replys.html.erb -> _posting_user_photo.mobile.erb. Then in layout.erb there are selections between _menu.mobile.erb, _menu.html.erb and some other layout issues. There can be multiple places where you have to choose between mobile and html inside multiple partials. Hypothetically it can be even more complicated: e.g. setting self.formats=[:full_version,:html,:text_only]. Then you would have fallback with text_only version of partials.

Now I have line <% self.formats = [:mobile, :html] if mobile_site? %> at the beginning of each mobile view. Works quite well.

When I started implementing mobile I thought it would work in the same way as how locales/languages work with more complex fallbacks. I wasn't expecting to have html as default fallback but I was excepting to be able to choose the fallback for each format. E.g. with locales you could have fallback with setting I18n.fallbacks[:"de-CH"] = [:de, :en]etc.

I would like to do this format-setting in the controller or by defining mime type with extra settings. So that if I want to replace any of existing views/partials with mobile version I could do it simply by creating a new partial with different ending. That method was easy to explain non ruby mobile ui designer.

But yea. I think now everything works as documented :) With couple views it's easy to have the one line in the beginning. With many view/partial files it's easy to patch FileSystemResolver as commented previously.

@jackdempsey

This comment has been minimized.

Show comment
Hide comment
@jackdempsey

jackdempsey May 10, 2012

Contributor

Stumbled into this. Really feels like Rails should provide a convention, and removing the mimetype from partials/templates doesn't seem right.

I got a custom resolver working locally. Perhaps a Rails blessed gem that easily lets you opt into the desired behavior would be good? (like jquery-rails). Would love thoughts from @josevalim @wycats

Contributor

jackdempsey commented May 10, 2012

Stumbled into this. Really feels like Rails should provide a convention, and removing the mimetype from partials/templates doesn't seem right.

I got a custom resolver working locally. Perhaps a Rails blessed gem that easily lets you opt into the desired behavior would be good? (like jquery-rails). Would love thoughts from @josevalim @wycats

@jackdempsey

This comment has been minimized.

Show comment
Hide comment
@jackdempsey

jackdempsey May 16, 2012

Contributor

@jdelStrother love the simplicity. Rather than always writing this out, I've packaged it up in a gem I'm calling resolvers, so others can make use as well. Like the responders gem from Plataformatec the gem will grow to provide other examples into writing small, useful resolvers. Thx for sharing!

https://github.com/jackdempsey/resolvers/blob/master/lib/resolvers/mobile_fallback_resolver.rb

Contributor

jackdempsey commented May 16, 2012

@jdelStrother love the simplicity. Rather than always writing this out, I've packaged it up in a gem I'm calling resolvers, so others can make use as well. Like the responders gem from Plataformatec the gem will grow to provide other examples into writing small, useful resolvers. Thx for sharing!

https://github.com/jackdempsey/resolvers/blob/master/lib/resolvers/mobile_fallback_resolver.rb

@jgarber

This comment has been minimized.

Show comment
Hide comment
@jgarber

jgarber Jun 8, 2012

Contributor

These mobile fallback resolvers (@jdelStrother and @jackdempsey) are great, but they stop working when you use any Rails engines. Devise, for example, has its own view path, so I get 'Template is missing' errors when trying to sign in or out.

Devise also uses views that include .html, so just leaving that out of the template name isn't an option (unless I'd fork their gem).

Anyone have a solution that works with Devise?

Contributor

jgarber commented Jun 8, 2012

These mobile fallback resolvers (@jdelStrother and @jackdempsey) are great, but they stop working when you use any Rails engines. Devise, for example, has its own view path, so I get 'Template is missing' errors when trying to sign in or out.

Devise also uses views that include .html, so just leaving that out of the template name isn't an option (unless I'd fork their gem).

Anyone have a solution that works with Devise?

@jackdempsey

This comment has been minimized.

Show comment
Hide comment
@jackdempsey

jackdempsey Jun 11, 2012

Contributor

interesting. I'm a big fan of devise so will try and take a look at this and make sure it can work happily together!

Contributor

jackdempsey commented Jun 11, 2012

interesting. I'm a big fan of devise so will try and take a look at this and make sure it can work happily together!

@jgarber

This comment has been minimized.

Show comment
Hide comment
@jgarber

jgarber Jun 11, 2012

Contributor

I figured it out. My problems weren't view paths like I assumed, but rather some Devise internals. The solution was here: https://github.com/plataformatec/devise/wiki/How-To:-Make-Devise-work-with-other-formats-like-mobile,-iphone-and-ipad-(Rails-specific)

Now with @jdelStrother's code snippet and the things from the Devise wiki page, it works perfectly. Thanks!

Contributor

jgarber commented Jun 11, 2012

I figured it out. My problems weren't view paths like I assumed, but rather some Devise internals. The solution was here: https://github.com/plataformatec/devise/wiki/How-To:-Make-Devise-work-with-other-formats-like-mobile,-iphone-and-ipad-(Rails-specific)

Now with @jdelStrother's code snippet and the things from the Devise wiki page, it works perfectly. Thanks!

@semaperepelitsa

This comment has been minimized.

Show comment
Hide comment
@semaperepelitsa

semaperepelitsa Aug 3, 2012

Contributor

I have the same problem with Kaminari. I can copy over their templates and remove "html" from all extensions, but this does not look right for me. Any other options?

Contributor

semaperepelitsa commented Aug 3, 2012

I have the same problem with Kaminari. I can copy over their templates and remove "html" from all extensions, but this does not look right for me. Any other options?

@semaperepelitsa

This comment has been minimized.

Show comment
Hide comment
@semaperepelitsa

semaperepelitsa Aug 3, 2012

Contributor

Ended up using the resolver above and the following code in controller:

class ApplicationController
  prepend_view_path Resolvers::MobileFallback.new(Kaminari::Engine.paths['app/views'].first)
end
Contributor

semaperepelitsa commented Aug 3, 2012

Ended up using the resolver above and the following code in controller:

class ApplicationController
  prepend_view_path Resolvers::MobileFallback.new(Kaminari::Engine.paths['app/views'].first)
end
@matthewrobertson

This comment has been minimized.

Show comment
Hide comment
@matthewrobertson

matthewrobertson Feb 20, 2013

Contributor

@Dorian @semaperepelitsa IMHO you shouldn't use request.format for this, you should use view paths. I created a gem that would probably help with what it sounds like you are trying to do.

Contributor

matthewrobertson commented Feb 20, 2013

@Dorian @semaperepelitsa IMHO you shouldn't use request.format for this, you should use view paths. I created a gem that would probably help with what it sounds like you are trying to do.

@Dorian

This comment has been minimized.

Show comment
Hide comment
@Dorian

Dorian Feb 21, 2013

Contributor

@matthewrobertson I use the view path notation now (my monkey path is really old now, I wish no one use it in production...). I will take a look at your gem.

Contributor

Dorian commented Feb 21, 2013

@matthewrobertson I use the view path notation now (my monkey path is really old now, I wish no one use it in production...). I will take a look at your gem.

@semaperepelitsa

This comment has been minimized.

Show comment
Hide comment
@semaperepelitsa

semaperepelitsa Feb 22, 2013

Contributor

Thanks for your suggestion. I think Rails 4 supports format fallbacks out-of-box now. But still, using view paths might be a better approach, I will try it next time.

Contributor

semaperepelitsa commented Feb 22, 2013

Thanks for your suggestion. I think Rails 4 supports format fallbacks out-of-box now. But still, using view paths might be a better approach, I will try it next time.

@mpapper

This comment has been minimized.

Show comment
Hide comment
@mpapper

mpapper Mar 31, 2013

After reading this thread, I think the correct way to accomplish what everyone seems to want (and something I implemented myself in rails 2 by monkey patching ActionView::Base::_pick_template) is the following.

This is almost the same as the above suggestions except it works with engines and ALL code infrastructure is detailed here so you know where to put which code.

Heres what I ended up doing that works with Rails 3.1 and engines. This solution allows you to place the *.mobile.haml (or *.mobile.erb etc.) in the same location as your other view files with no need for 2 hierarchies (one for regular and one for mobile).

Engine and preparation Code

in my 'base' engine I added this in config/initializers/resolvers.rb:

    module Resolvers
      # this resolver graciously shared by jdelStrother at
      # https://github.com/rails/rails/issues/3855#issuecomment-5028260
      class MobileFallbackResolver < ::ActionView::FileSystemResolver
        def find_templates(name, prefix, partial, details)
          if details[:formats] == [:mobile]
            # Add a fallback for html, for the case where, eg, 'index.html.haml' exists, but not 'index.mobile.haml'
            details = details.dup
            details[:formats] = [:mobile, :html]
          end
          super
        end
      end
    end

    ActiveSupport.on_load(:action_controller) do
      tmp_view_paths = view_paths.dup # avoid endless loop as append_view_path modifies view_paths
      tmp_view_paths.each do |path|
        append_view_path(Resolvers::MobileFallbackResolver.new(path.to_s))
      end
    end

Then, in my 'base' engine's application controller I added a mobile? method:

    def mobile?
        request.user_agent && request.user_agent.downcase =~ /mobile|iphone|webos|android|blackberry|midp|cldc/ && request.user_agent.downcase !~ /ipad/
    end

And also this before_filter:

    before_filter :set_layout

    def set_layout
      request.format = :mobile if mobile?
    end

Finally, I added this to the config/initializers/mime_types.rb:

    Mime::Type.register_alias "text/html", :mobile

Usage

Now I can have (at my application level, or in an engine):

  • app/views/layouts/application.mobile.haml
  • and in any view a .mobile.haml instead of a .html.haml file.

I can even use a specific mobile layout if I set it in any controller:
layout 'mobile'

which will use app/views/layouts/mobile.html.haml (or even mobile.mobile.haml).

mpapper commented Mar 31, 2013

After reading this thread, I think the correct way to accomplish what everyone seems to want (and something I implemented myself in rails 2 by monkey patching ActionView::Base::_pick_template) is the following.

This is almost the same as the above suggestions except it works with engines and ALL code infrastructure is detailed here so you know where to put which code.

Heres what I ended up doing that works with Rails 3.1 and engines. This solution allows you to place the *.mobile.haml (or *.mobile.erb etc.) in the same location as your other view files with no need for 2 hierarchies (one for regular and one for mobile).

Engine and preparation Code

in my 'base' engine I added this in config/initializers/resolvers.rb:

    module Resolvers
      # this resolver graciously shared by jdelStrother at
      # https://github.com/rails/rails/issues/3855#issuecomment-5028260
      class MobileFallbackResolver < ::ActionView::FileSystemResolver
        def find_templates(name, prefix, partial, details)
          if details[:formats] == [:mobile]
            # Add a fallback for html, for the case where, eg, 'index.html.haml' exists, but not 'index.mobile.haml'
            details = details.dup
            details[:formats] = [:mobile, :html]
          end
          super
        end
      end
    end

    ActiveSupport.on_load(:action_controller) do
      tmp_view_paths = view_paths.dup # avoid endless loop as append_view_path modifies view_paths
      tmp_view_paths.each do |path|
        append_view_path(Resolvers::MobileFallbackResolver.new(path.to_s))
      end
    end

Then, in my 'base' engine's application controller I added a mobile? method:

    def mobile?
        request.user_agent && request.user_agent.downcase =~ /mobile|iphone|webos|android|blackberry|midp|cldc/ && request.user_agent.downcase !~ /ipad/
    end

And also this before_filter:

    before_filter :set_layout

    def set_layout
      request.format = :mobile if mobile?
    end

Finally, I added this to the config/initializers/mime_types.rb:

    Mime::Type.register_alias "text/html", :mobile

Usage

Now I can have (at my application level, or in an engine):

  • app/views/layouts/application.mobile.haml
  • and in any view a .mobile.haml instead of a .html.haml file.

I can even use a specific mobile layout if I set it in any controller:
layout 'mobile'

which will use app/views/layouts/mobile.html.haml (or even mobile.mobile.haml).

@benoittgt

This comment has been minimized.

Show comment
Hide comment
@benoittgt

benoittgt Jan 26, 2016

Contributor

With this example

It fails with the last security patch on rails 4.2.5.1

    ArgumentError:
         wrong number of arguments (5 for 4)

Adding outside_app_allowed fixe the issue. But doesn't it mean that this module should be update to reflect the last security patches ?

Contributor

benoittgt commented Jan 26, 2016

With this example

It fails with the last security patch on rails 4.2.5.1

    ArgumentError:
         wrong number of arguments (5 for 4)

Adding outside_app_allowed fixe the issue. But doesn't it mean that this module should be update to reflect the last security patches ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment