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.
Related
For my application I have a couple of really large bundles and I want the user to be able to skip (specific) updates to the content.
At the moment I'm using the Addressables.CheckForCatalogUpdates() which lets me know If there's an update.
But using this function already lets my application know there's a new version of the assets and as soon as I load the asset it wants to use the new (still to download) version.
Ideally would like to end up with a flow similar to:
Application checks for updates
User gets prompted if he/she updates
=> User updates
New content is downloaded
=> User skips updates
content is loaded from cache
Application continues
Help is much appreciated!
Regards,
Gijs
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.
I want to serve static HTML pages using nginx. Then, I will use jQuery to update DIVs, SPANs, etc via AJAX calls from a Padrino server.
I like creating my web pages in HAML because it's easier but in production, I don't want to serve HAML templates. Just raw, HTML at the speed of nginx.
Is there an easy way to do this?
What would be ideal would be a service that automatically renders HAML, partials, etc into the public folder that nginx could serve.
Simple,
add padrino-cache to your app
class SimpleApp < Padrino::Application
register Padrino::Cache
enable :caching
get '/foo', :cache => true do
expires_in 30 # expire cached version at least every 30 seconds
'Hello world'
end
end
Then save wherever you want to serve it:
set :cache, Padrino::Cache::Store::File.new(Padrino.root('public'))
You can read more here: http://www.padrinorb.com/guides/padrino-cache
First thing that pops to my mind would be Jekyll. Anyway I see it only as a matter or optimization, so if you already have a Sinatra, you could start by rendering HAML on every request, and than add caching.
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'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.