How do I make a separate View path/directory for iPhone in Rails? - iphone

I'm making an iPhone version of an existing Rails app. I'd like to make the mobile version accessible via a subdomain such as iphone.mysite.com.
I know I can use formats and the respond_to block for individual erb files, such as index.iphone.erb as show here:
Creating an iPhone optimised version of your Rails site using iUI and Rails 2
But I'd like to keep entirely separate view directories for the mobile version and regular version such as this:
app/views/iphone
Here's what I've tried in my Application controller:
class ApplicationController < ActionController::Base
before_filter :set_site
def set_site
subdomain=self.request.subdomains[0]
ActionController::Base.prepend_view_path("app/views/#{subdomain}")
end
When testing this, however, the view switches to the view associated with the last requested subdomain by any user.
For example, if I visit http://iphone.mysite.com, then immediately go to http://www.mysite.com in another separate browser, I see the mobile version instead of the regular one. Refreshing it will correct this and bring up the right version. But if I go back to http://iphone.mysite.com in the other browser and refresh, it brings up the non-mobile site! I'm tearing my hair out and not understanding what's going on.
Any advice would be much appreciated.
Edit 1
Vlad below found a link with a possible solution however it is not working for me. Here is the code I tried. I made a file called subdomain_view.rb and placed it in config/initializers:
# Put all of this in a bootstrap-only initializer
ActionController::Base.class_eval do
APP_ONE_VIEW_PATH = "app/views/iphone"
APP_TWO_VIEW_PATH = "app/views/default"
cattr_accessor :application_view_path
self.view_paths = ["app/views", APP_ONE_VIEW_PATH, APP_TWO_VIEW_PATH]
# This is where you determine the switching mechanism for your application. Here, it is a simple GET parameter.
# You can probably argue that this specific piece SHOULD be in your actual app_controller class definition, as it is the only piece
# of info pertinent to the rest of your application.
before_filter do |controller|
ActionController::Base.application_view_path = request.subdomains[0]=="iphone" ? APP_TWO_VIEW_PATH : APP_ONE_VIEW_PATH
end
end
require 'aquarium'
ActionView::PathSet.class_eval do
include Aquarium::DSL
before :find_template do |join_point, object, *args|
object.each_with_index do |path,i|
object.unshift(object.delete_at(i)) if path.to_s == ActionController::Base.application_view_path
end
end
end
# I'll leave the exercise of testing this or implementing it for your particular app up to you.
With the above code, I am getting the same view no matter what subdomain I put in. Any suggestions on what might be wrong? Am I putting this code in the wrong place?

First, you have an error in your approach. You only 'set' a view path, you don't 'unset' it. When you do something like
ActionController::Base.prepend_view_path
this actually persists between your requests (internally, you are setting a class variable, and because classes are cached in production, this variable remains set between requests). Therefore, the current view path is the view path of your last request.
IMO, you should dynamically compute view_path for your current subdomain (this implies some ActionView hacking). A nice solution is provided here.

I was able to solve the problem by using a gem called themes_for_rails:
https://github.com/lucasefe/themes_for_rails
After installing the gem, here's what I added to my application files:
#application_controller.rb
class ApplicationController < ActionController::Base
theme :theme_resolver
def theme_resolver
current_subdomain=self.request.subdomains[0]
end
end
#routes.rb
MyAppName::Application.routes.draw do
themes_for_rails
end
#Gemfile
gem 'themes_for_rails'
I placed my themes in [application_root]/themes. Make sure you don't put it in [application_root]/app/themes.

Related

Using iron-router with angular-meteor makes controllers being loaded twice

I have a Meteor application using angular-meteor. I need now to load different angular modules depending on url. I added iron-router to my application to do so and I continue to handle routes for each module using ngRoute and anchor nav but it behaves strangely if url contains params. I made a small test case which is available here:
https://github.com/clouchtibat/iron-router-ng-route
If you click on 'truc' link and then on 'test', next routes changes will make controller be instantiated two times. It works if urls have no params.
I also tested with ui-router (in the with-ui-router branch) and the problem is the same but in addition view is duplicated.
Is this a bug in one of the two routers or is there something wrong with my implementation?
Take a look at this conversations in the angular-meteor Github issues:
https://github.com/Urigo/angular-meteor/issues/154
https://github.com/Urigo/angular-meteor/issues/493
I think it can help you with some directions.
I am also having some hard time with mixin angular-meteor and iron:router.

Migrate custom Facebook util library to Yii framework

I have a facebook app developed in plain PHP, I'm migrating the app to YII framework.
The thing is that I use a class call "utilsFacebook" where I have the object facebook(of the fb sdk) and all the methods that I need to get data from facebook, getUserId, getUserFriendList, etc.
I don't know how to handle all the operations that I do in utilsFacebook with Yii.
Create a controller with the functions of utilsFacebook is the correct think to do?
Every time that I instance the controller would create a new Facebook object, Should I store that object in a SESSION to get a better performance or is a bad idea?
Q. Create a controller with the functions of utilsFacebook is the correct think to do?
Having done a facebook app using yii as the framework, i would recommend you to make this library either a component, or an extension.
But definitely don't put these functions in the controller directly. Whenever a controller needs them call the functions using your custom facebook util class.
Components can be put in the folder: projectrootfolder/protected/components
Extensions can be put in the folder: projectrootfolder/protected/extensions
If you don't believe that either of these make semantic sense, you can always create a new folder within protected, say utils and put the class there. However i think extensions is the best way to go.
Q. Should I store that object in a SESSION to get a better performance or is a bad idea?
I don't think it's necessary to store the object in a session, because there will be no visible performance gain. Further you'll complicate your code unnecessarily.
What i had done was, created an app level component and used this component throughout the app, in any controller.
Example:
In your application's config, protected/config/main.php :
'components'=>array(
'fbHelper'=>array( // gave the component this name
'class'=>'ext.utils.FacebookHelper', // had stored the helper class in extensions/utils folder
'parameter1'='somevalue',
// more parameters
),
// standard yii app components
),
This will allow you to use the component like this: Yii::app()->fbHelper->getFriends();
Take a look at the facebook-opengraph extension, which could help you, on the way.

Converting a Brownfield PHP Webapp to Zend Framework

We're thinking of converting our PHP Webapp from using no framework (which is killing us) to use Zend Framework. Because of the size of the application I don't think starting from scratch is going to be a viable option for management so I wanted to start researching how to slowly convert from the current site structure to one using Zend Framework but there isn't a lot of information on this process.
So far my plan is to dump the current code base into the public/ directory of the Zend Application, fix the numerous problems that I'm sure this will crop up and then start rewriting modules one at a time.
Has anyone had experience doing this in the past and how did it work out for you?
I've done a few of these now. What worked best for me was putting ZF 'around' the old app, so all requests go through ZF. I then have a 'Legacy' controller plugin, which checks whether the request can be satisfied by ZF, and if not, sends it to the old app:
class Yourapp_Plugin_Legacy extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();
if (!$dispatcher->isDispatchable($request)) {
// send to the old code...
}
}
}
exactly how you then send the request to your old app depends a bit on how it is implemented. In one project, I examined the request, determined what file from the old code the request would have gone to, and then required that in. It sounds like this might be appropriate for you. In another project my solution was to route all these requests to a LegacyController in the ZF project, which ran the old code to get the resulting HTML and then rendered it inside the Zend_Layout from the new project.
The advantages of this approach are that you can gradually introduce ZF modules as you rewrite parts of the old app, until you reach the point where 100% of requests can be served by ZF. Also, since the ZF project has initialized before your old code is run, your old code can use the ZF autoloader, so you can start replacing classes in the old code with models written in a more ZF-style, and have them used by both parts of the app.

zend framework under document root in subdir

I developed a application with Zend Framework and now I want to be able to place the app in an subdirectory of a Documentroot.
e.g. http://www.example.com/myapp/
I read quite a lot of Docu how this could work, but all in all these solutions don´t fit my needs. Is there a trivial way to do the subdir thing, without adding the concrete path to any file which generates the pages.
There are some examples in the net, where a basePath is set in the application enviroment and so there is a method call bevor each "form" creation which prepends the path before the link.
$form->setAction($this->_request->getBaseUrl() . $this->_helper->url('sign'));
This was from: http://johnmee.com/2008/11/zend-framework-quickstart-tutorial-deploy-to-a-subdirectory-instead-of-web-root/
But this is only works for small examples, I have tons of forms, tons of views and tons of scripts. I can´t belive this (lets call it hack :) ) is the only solution to do this.
Any ideas?
You don't have to do anything special. See my tutorial at http://akrabat.com/Zend-framework-tutorial which is developed entirely within a sub-directory.
As they say on the web page:
I’m told this last issue has been
lodged has a defect and not necessary
from releases “1.7″ and beyond. The
helper->url will henceforth prepend
the baseUrl to its result.
So you should be fine. Do you actually use the $form->setAction() method on every form already? Because if you use it in combination with the url helper, the baseUrl will already be included.

Mobile version of views for Ruby on Rails

I'm after some validation that I'm doing the right thing. I have my Ruby on Rails application in the following structure:
/home
about.rhtml
index.rhtml
/display
index.rhtml
/data <--This is called by jQuery from the display\index page to provide the data to render
push.js.erb
pull.js.erb
/layout
home.rhtml
display.rhtml
Everything is working fine, but I now want to add a site targeted for mobile devices. While the iPhone renders the website correctly, it would be nice to provide a more targeted experience. Ideally, I'm thinking about having an iPhone.domain.com which would be redirected to via .htaccess.
For this, I was thinking about adding another view for each device
/iPhone
home.rhtml
about.rhtml
display.rhtml
However, it feels like a lot of the data would be duplicated, for example the about page would be in two places. I guess I could have a partial and do something like render :partial => 'home/about' but that seems a little hacky.
How can I develop my site to support this?
I was thinking about a structure such as, but again not sure how to structure the code - how do I tell it to render the view in the iPhone directory... while not having the master layout applied
/display
/iphone
index.rhtml
I would really like some advice on the best way to approach this and structure the application. While the applications follow a structure at the moment, they could go off in different directions..
Thank you
Ben
I would strongly recommend leaving the controller structure the same across all device types. Particularly if you are using Rails' RESTful routes your controllers should be closely matched to the domain model of your data. Whether that data is then presented to a desktop browser, to an iPhone, to a different type of mobile device, to a JSON/XML REST API client etc. is mostly a matter of the presentation layer, not the controller/routing layer.
So an elegant solution would be:
Detect device type based on User Agent (you may want to refer to the WURFL User Agent database);
use Rails' respond_to mechanism to render a different view format for each device type;
define a layout for each device type (e.g. using the XHTML Mobile Profile doctype for mobile devices);
include different CSS files depending on device type.
There are some plugins which try to make this easier: have a look at brendanlim's Mobile Fu and noelrappin's Rails iUI (both on GitHub). Also Brendan Lim's presentation at Rails Underground has a few ideas.
What you should be aiming for is something like:
def show
#foo = Foo.find(params[:id])
respond_to do |format|
format.html # => show.html.erb
format.iphone # => show.iphone.erb
format.blackberry # => show.blackberry.erb
end
end
You should also allow users on mobile devices to override the user agent detection if they really want to see the desktop version of the site. A cookie with a long expiry time is probably the best way to do this, so that the site remembers the choice next time the user returns. Some mobile devices have rubbish cookie support, but then they probably won't want the desktop version of the site anyway because it probably won't work.
Rails 4.1 includes Variants, a great new feature that:
Allows you to have different templates and action responses for the same mime type (say, HTML). This is a magic bullet for any Rails app that's serving mobile clients. You can now have individual templates for the desktop, tablet, and phone views while sharing all the same controller logic.
In your case, you only need to set the variant for the iphone in a before_action, e.g:
class HomeController < ApplicationController
before_action :detect_iphone
def index
respond_to do |format|
format.html # /app/views/home/index.html.erb
format.html.phone # /app/views/home/index.html+phone.erb
end
end
private
def detect_iphone
request.variant = :iphone if request.user_agent =~ /iPhone/
end
end
What's new in Rails 4.1
The Iphone actually does a pretty good job of rendering web pages without any special formatting.
However on my Android phone floated content seems to get cut off and so a custom view for that phone is required.
To achieve this you need to create a different layout (e.g. mobile_application.html.erb) and in your application_controller add the following:
layout :select_layout
def select_layout
session.inspect # force session load
if session.has_key? "layout"
return (session["layout"] == "mobile") ? "mobile_application" : "application"
end
return detect_browser
end
def detect_browser
agent = request.headers["HTTP_USER_AGENT"].downcase
MOBILE_BROWSERS.each do |m|
return "mobile_application" if agent.match(m)
end
return "application"
end
where MOBILE_BROWSERS is a an array of user agent strings you want to match as a mobile device.
I wrote a blog about this here:
http://www.arctickiwi.com/blog/2-mobile-enable-your-ruby-on-rails-site-for-small-screens
Cheers
firstly, you should be using .html.erb as your template extension
secondly you can use logic to detect the type of layout to use based on the user agent (request.user_agent).
layout :site_layout
def site_layout
some_way_to_detect_the_layout_to_use
end
Note, the user_agent can be faked, but a majority of people wont bother faking it so the solution should be "good enough" for 99.9% of cases.