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.
Related
I built an extension and a plugin where frontend-users can edit their profile but I noticed a critical issue:
Under "Edit profile", users could see the full information about another user who wasn't even logged in. Apparently the form was a cached on the server because after adding:
config.no_cache = 1
it didn't happen again. Now the issue is that indexing is disable on the whole website.
Is there a way to disable caching only for this specific extension / plugin ?
You should have something like this in your ext_localconf.php :
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
$_EXTKEY,
'List',
array('User' => 'list,editProfil'),
array('User' => 'editProfil') // Uncached actions
);
Here is where it is explained : https://docs.typo3.org/typo3cms/ExtbaseFluidBook/4-FirstExtension/7-configuring-the-plugin.html
If you want this to only apply on specific pages or be controllable by integrators, you can override the TS rendering instruction for the object:
tt_content.list.20.YOURLISTTYPEHERE = USER_INT
Or if you registered it as a custom CType:
tt_content.YOURCTYPEHERE.20 = USER_INT
The above should work for fluid_styled_content and css_styled_content.
It is almost never advisable to use config.no_cache = 1 since this disables a lot of things other than just caching, as you found out. It also disables all caching for the entire page and it is almost always better for performance to only make the specific plugin not cacheable - and if possible, only do so on pages where the plugin gets used to render views which should not be cached.
Be careful if you do end up needing to cache some parts of your view. It isn't a silver bullet in terms of security, but it is a good start to always include the user's ID (and possibly other things from the authentication as well) in any cache identifiers. And try not to store sensitive information in caches at any point, including code where you output things like the user's name.
You know how Temple Run sometimes has alerts when you open the App that appear even though you don't update the App? I understand how you would implement this if you were to submit an update to your App, but how does Imangi implement new alerts without releasing new versions of the App? (I'm assuming they upload it from some server, but I'm an amateur at all of that stuff so could someone sorta vaguely explain how I might go about doing that? Will I need to learn Internet programming languages :O?)
Thanks.
I agree with Jonathan. I would set a plist with a reference number on your server. and it would look something like this. I'm using concept, not code. It would be as simple as hosting it on your server. Or it could be as complicated as your creating a user interface on your website that allows you to just plug in the information and it would create the plist for you.
-(void)checkanddisplaynotificationbasedonupdatedplistontheserver{
int currentnotificationnumber = userprefs preference for item "notification"
get and parse notification.plist from your server
notificationnumber = object at index 0
if notificationnumber > currentnotificationnumber{
display your notification with parsed plist
}
}
You could host a plist online, with an array of alerts stored as dictionaries, with attributes like 'title', 'body' etc. The app would then parse this and to create an alert. You could then set up a method which searches for updates to this file every time the app opens and has connectivity.
This is not the only way - there are probably hundreds of other files types/ automated systems to use, however this is a simple way, and roughly how all of them work, and I have implemented something like this in some of my apps. Hope this helps, if you wan't any help coding it, I will be happy to help!
Jonathan
I'm implementing a web application which will support different views according to different browsers. For example, In mobile browsers, it will show a smaller view to users with less UI elements. But we'd like to use same presenters.
I have a solution on hand - adding browser type detecting logic in ClientModule, e.g:
if (browser == "iphone") {
bindPresenter(HomePresenter.class, HomePresenter.MyView.class, HomeView.class, HomePresenter.MyProxy.class);
} else if (browser == "ipad") {
bindPresenter(HomePresenter.class, HomePresenter.MyView.class, IPadHomeView.class, HomePresenter.MyProxy.class);
} else {
bindPresenter(HomePresenter.class, HomePresenter.MyView.class, IPhoneHomeView.class, HomePresenter.MyProxy.class);
}
I'm wondering if it is possible to use some ways like deferred binding in GWT-platform. (but I'd like to follow GWT-plarform's structure rather than adding deferred binding code in xxx.gwt.xml).
So my questions are:
1) Are there any other ways to implement the feature mentioned above?
2) Which way is the best, and why?
Thanks in advance!
Best regards,
Jiakuan W
There is an example in the gwt samples folder that does something like you are wanting. I use a version of the sample code in my project -except using Gin to handle the clientfactory functionality. The sample is called mobilewebapp. It involves using a formfactor method in your .gwt.xml to determine which system you are on - in this case it breaks it down into desktop, mobile, and tablet. Then later in your gwt.xml it trades out client factories based on the form factor - I trade out gin models instead. Here is a link to the source for mobilwebapp
GWT does not allow you to set custom user agent types. You're limited to their set of gecko, gecko1_7, safari, IE6, IE7, IE8, and opera.
That being said, you can access the user agent directly and set your logic to switch accordingly with Window.Navigator.getUserAgent(), or via a property provider.
See this similar question on how to do mobile browser detection in GWT for MVP.
Check the gwtp google group, its a good source, and someone posted a pdf about his efforts regarding the sake problem in there.
Anyway, if I recall correctly, he holds multiple gin modules for each client with the presenters and views, runs custom js code on loading and than installs the correct module on the the ginClinet class.
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.
I starting to investigate whether Ruby-on-Rails will help solve my problem in some way?
In short, I have a legacy application which is reading and writing a collection of images. This application also uses a sqlite database to help refer to correct image files when required.
What I'd like to do is have an iPhone and/or iPad application browse the images, but also be able to see updates and additional images without hitting a refresh button; or using a periodic timer say.
This brought me to investigate Ruby-on-Rails as a solution?
I made the following diagram to think about whether I'm on the right path:
To date I obviously have the (legacy) in-place linux app, which generates images and updates the database. I have also embedded a bonjour service inside the linux app to allow an iPhone application to connect and to-date browse thumbnails that are inside the sqlite database. The tricky part is that I've realised that the iPhone app needed to be kicked to update its images when the database is changed remotely.
Actually I thought that with a ruby-on-rails app it could provide a RESTful service to access fullsize (plus, thumbnail) images allowing the thumbnail images to be removed from the sqlite database - it's likely the database could have 10000+ entries, so didn't want the db to grow unnecessarily.
Any tips or resources would be great.
Thanks.
Update: I had a think about the wording of my question again. I guess I was trying to figure out if I could get something like Rails to monitor changes to a database without polling in some way.
The simplest solutions I can think of is to have an action on your RESTful service which pulls the most recently modified date from the database. Periodically query this from your iPhone app, and then refresh the list.
For example, if you create scaffolding for images:
rails generate scaffold Image name:string filename:string # fill in the other attributes you need
NOTE: You will have to sync your legacy DB to this Rails table
Then each image object is automatically created with an "updated_at" attribute.
class ImagesController < ApplicationController
# GET /images
# GET /images.xml
def index
#images = Image.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #images }
end
end
# GET /images/1
# GET /images/1.xml
def show
#image = Image.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #image }
end
end
def newest
#image = Image.find(:last, :order => :updated_at)
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #image }
end
end
# more methods down here
end
and you'll have to add the following match line to your routes.rb
YourAppName::Application.routes.draw do
match 'images/newest' => 'images#newest'
resources :images
end
Now, from the iPhone app you can pull an XML file describing your most recent image with this URL
http://localhost:3000/images/newest.xml
Parse out the most recently updated time, and compare it to your last refresh date.
One possible answer would be to use Apple's push notification service, it would work outside your application as a bonus.
It really depends on what you want to provide in your application. You could just schedule requests from your application to your server (which could be a Ruby app), so you poll for updates. That's probably the easiest solution.