Rails - select different view template based on requests - ruby-on-rails

I am implementing a REST API which is versioned(like Twitter API), so based on version in request, I need to render template specific to the version, for example, if the client requests:
http://www.foo.com/api/v1/posts.json
I'd like to have the controller render:
posts/index.v1.json.erb
but if the client requests
http://www.foo.com/api/v2/posts.json
I'd like to have the controller render:
posts/index.v2.json.erb
and so on.
the version number in URL will be put in params hash in route.rb.
I want to do this in a reusable way, so it's not acceptable to repeat the logic in specific controller action.
I have tried view resolver, however it doesn't have access to request so there is no way I can pass the version number to resolver.
is there any way to accomplish this?
Thank you!
-Xiaotian

You can specify the version number in your routes.rb like:
map.connect '/api/:version/posts', :controller => :api, :action => :index, :version => :version
Then you would have access to the version in your controller via params[:version] and can handle it appropriately.

I think I would suggest making a separate controller for each version, maybe in the same module
Api::VersionOneConroller
Api::VersionTwoConroller
or something like that, I am not familiar with your whole app so I cannot say whether that will work, buts its something to consider.
------------update-------------
if the difference in versions in only in output foramtting or somehting, and all the actions do the same thing you could add after filters
class ExampleContrller < ApplicationController
after_filter :manage_versions
...
end
ApplicationController < ActionController::Base
protected
def manage_versions
case params[:version]
when '1.0'
#response to xml
when '1.2'
#response to json
else
# err or default to one
end
end
end
something like that could work
read more about filters here http://rails.rubyonrails.org/classes/ActionController/Filters/ClassMethods.html

Related

Dynamic Routes Rails 4, taken from db

Frustrating, I can't find an eligible solution for my problem.
In my Rails 4 app, I want to give my users the possibility to add their own custom post types to their sites. Like:
www.example.com/houses/address-1
www.example2.com/sports/baseball
Both would work, but only for the linked sites. Sports and houses would be the (RESTful) post types, taken from the db, added by users.
I have been struggling to find a elegant solution to accomplish this. I found http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4 but that feels kinda hacky and I'm not sure if reloading the routes works in production, I'm getting signals that it won't.
I'd say I have to use routes constraints http://guides.rubyonrails.org/routing.html#advanced-constraints but I don't have a clue how to approach this.
To be clear, I have no problem with the site setting stuff, the multi tenancy part of my app is fully functional (set in Middleware, so the current site is callable in the routes.rb file). My issue is with the (relative) routes, and how they could be dynamically set with db records.
Any pointers much appreciated.
I think route constraints don't work for you because your domain is a variable here. Instead, you should be examining the request object.
In your ApplicationController, you could define a method that would be called before any action, like so:
class ApplicationController < ActionController::Base
before_action :identify_site
def identify_site
#site = Site.where(:domain => request.host).first
end
end
As you scale, you could use Redis for your domains so you're not making an expensive SQL call on each request.
Then you can just add the #site as a parameter to whatever call you're making. I'm assuming you're doing some sort of "Post" thing, so I'll write some boilerplate code:
class PostController < ApplicationController
def show
#post = Post.where(:site => #site, :type => params[:type], :id => params[:id])
end
end
Just write your routes like any other regular resource.

Rails - Add custom headers to response based on API version

What I am trying to do is pretty simple. There are multiple versions of a Rails REST API. So, there are routes like:
http://www.example.com/v1/user.json
http://www.example.com/v2/user.json
http://www.example.com/v3/user.json
What I want to do is add custom http headers to the response based on the API version endpoint that is requested.
In my config/application.rb file, I tried:
config.action_dispatch.default_headers.merge!('my_header_1' => 'my_value_1', 'my_header_2' => 'my_value_2')
I have also tried this in my config/routes.rb file:
scope path: "v1", controller: :test do
get "action_1" => :action_1
get "action_2" => :action_2
Rails.application.config.action_dispatch.default_headers.merge!('my_header_1' => 'my_value_1', 'my_header_2' => 'my_value_2')
end
But both of these snippets append custom headers to the response irrespective of the API version endpoint.
I think I can write a middleware that checks the request url and appends the response headers based on that but it sounds a bit hackish.
Is there a better way to achieve this? Preferably via config or some central piece of code?
What about using a before_action on your controllers? I imagine each API version has its own controllers? That way you could do something like:
class API::V1::BaseController < ApplicationController
before_action :set_headers
protected
def set_headers
response.headers['X-Foo'] = 'V1'
end
end

Accessing a variable of one method in another in ruby on rails

I am facing an issue with accessing a particular variable of a method say A , in another method say B in the controller.. The size of the object(variable) is too big since it contains the results of a service call made.. My usecase is like on selecting an option from a drop down box, it redirects to a method B in controller and the same object(variable) should be parsed. How can I access the variable in the other method?
I tried storing in a cookie and since the size is too big I am getting Cookie Overflow exception. I am not using a DB. So I guess using memcache won't work. Also tried storing it as hidden field in view and passed its value as a data through ajax call. But I am getting it as a string. Tried to specify datatype as json and several other ways.. but of no use..Using ##var also din work..Not sure why..
Code:
On change of the drop down:
$(document).ready(function(){
$('#filter_service').change(function() {
$.ajax({type: "GET",
url: "/device_troubleshootings/query_operation",
data: { filter_service: $('# filter_service').val()},
});
});
});
Service call:
def log_results
//Service call
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
Here, I am trying to access #dsn_result object in "/device_troubleshootings/query_operation” method.
Please suggest me ways to access the variable.
MVC
I think you're getting confused with how Rails should work
Remember, Rails (which is just a framework for Ruby) is built on the "MVC" programming pattern. This means each time you send a request to your Rails application, it has to be handled by a single controller#action which you will then allow you to pull the relevant data from your models
The problem you have is you're trying to load multiple controller methods, and pass the same data to both. This might work in Ruby, but not Rails (Rails is stateless):
--
Model
The correct way to handle this type of setup is by creating another request for your application, which will load another controller#action, allowing you to access the data you need
As demonstrated by the MVC diagram above, each time you send a request to Rails, it's basically a new request. This means that unless you've persisted your data in the likes of a cookie, you'll need to load the data from the model.
The problem you have is you're trying to store an entire data-set in the front-end of your system. This issue is very bad, as not only is it inefficient, but it goes against the MVC pattern completely.
You'll be much better storing the bare-minimum data set you need in the front-end (ids or similar), which you will then be able send to your controller via ajax; building a new data-set from
--
Class Variables
You mentioned you tried to declare some ##class variables to no avail. The problem with this is that the class vars will only be available for an instance of a class.
As mentioned, since Rails is stateless, the class variables won't persist between requests (how can they?). I think you know this already, considering you've been trying to use cookies to store your data
The way to resolve this is to rebuild the data each time from the model (as detailed above)
Solution
The solution for you is to "go stateless"
Here's how:
Treat Method A and Method B as completely separate "ACTIONS"
When using these actions, you need to consider the smallest piece of data to pass between the two
To load Method B, you need to send a new request from your browser (as if you've never loaded Method A before)
Your method_a can be handled in the "standard" way:
#config/routes.rb
resources :your_controller do
collection do
get :method_a
get :method_b
end
end
This will mean that you can load method_a relatively simply:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def method_a
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
end
As you know, the #dsn_result will not persist through to the next request.
There are two ways to resolve this (set a CONSTANT -- if you're pulling from an API, this will give you a single call -- or use a before_action to set the variable for as many actions as you need). I'll detail both for you:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
before_action :set_log_data
def method_a
end
def method_b
end
private
def set_log_data
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
end
This will work if you pull data from your own data-set (using the models), however, the better way to do this in your case will likely be to set a constant (considering, of course, that you don't want the data to change):
#config/initializers/dsn_result.rb
get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
DSN_RESULT = get_log_results.logs_result_obj_list
In my case I solved with global variable $my_global_var
So my files look like this
routes.rb
Rails.application.routes.draw do
resources :pages
root 'pages#index'
post 'pages/test'
end
pages_controller.rb
class PagesController < ApplicationController
def firstaction
$my_global_var = "My global var"
puts $my_global_var
end
def secondaction
puts $my_global_var
end
end
index.html.erb
<%= button_to 'Test', pages_test_path, method: :post %>

Rails 3.0 Mobile Site

I was wondering how I go about creating a mobile version of a Rails 3.0 application.
I saw this post: Mobile version of views for Ruby on Rails
But I am confused on the respond_to method. How does the method know which format to render?
Would I create a method in my application controller to render a mobile layout and then for each view use the respond_to method?
Thank you,
Brian
Ryan Bates has done a great tutorial
http://railscasts.com/episodes/199-mobile-devices
The respond_to method will choose according to the current request's mime type.
This works out of the box for common mime types, but you'll need to tell your application about your custom ones. In your application controller, you'll want to define a method that will adjust the format of Rails' internal reprensentation of the request. Then, call that method as a before filter. Here's an example:
class ApplicationController < ActionController::Base
before_filter :adjust_for_mobile
def adjust_for_mobile
request.format = :mobile if mobile_request
end
# You'll also need to define the mobile_request method
# using whatever strategy you want to tell if a request
# is from a mobile client or not
def mobile_request
true
end
end
Make sure you've defined this new type in config/initializers/mime_types.rb:
Mime::Type.register "text/html", :mobile
Then, in your controllers, you'll be able to use the 'mobile' format:
class FoosController < ApplicationController
def index
#foos = Foo.all
respond_to do |format|
format.html # index.html.erb
format.mobile # index.mobile.erb
end
end
end
This sure looks elegant and all but in practice, I find that I rarely use it for mobile sites. The mobile sites I've been working on are generally quite different from the 'complete' sites. In those cases it makes sense to just define another bunch of controllers under a 'mobile' namespace.
Have a look at Rails Mobile
I have developed that plugin a while back. The idea behind that plugin is you can redirect to different controllers or views based on your mobile device capabilities through your router config file.
At the end of the routing.rb add these lines:
MobileDispatch::Categories.add do
def mobile_classifier(device)
"_mobile"
end
end
These lines define a new substring for all mobile devices which will be stored in $ variable for each request in the rouging.rb file.
That way you can play with your routing rules. For instance this line in routing.rb:
match '/photo/:id', :to => "photo#index$", :classifier => :mobile_classifier
for a normal user would be interpreted as:
match '/photo/:id', :to => "photo#index", :classifier => :mobile_classifier
while for a mobile user as:
match '/photo/:id', :to => "photo#index_mobile", :classifier => :mobile_classifier
The power here is in mobile_classifier(device) method where you can return different classification based on device object.
so let say we modify the method to return "_iphone" for all iphone devices and "_android" for all android mobiles, then the above routing line would be interpreted as:
match '/photo/:id', :to => "photo#index_iphone", :classifier => :mobile_classifier
match '/photo/:id', :to => "photo#index_android", :classifier => :mobile_classifier
If you add the $ to the end of view part of each route (similar to what we did here) you will get different methods in your controller for each category of devices and different view names for each method (index_iphone.htm.erb and index_android.ht.erb) This way you have seperate views/layers for each device category that you defined in your mobile_classifier method.

In Rails, is there a way to call request.request_uri in a class (not a controller)?

I'm building a pageview counter for my app using the Garb Ruby wrapper for the Google Analytics API. Doing so means creating a new Module in the 'lib' folder, in which I create a class like this:
#lib/Analyze.rb
...
class Report
extend Garb::Resource
metrics :pageviews
dimensions :page_path
filters :page_path.eql => '/' #the path of the page I instantiate this class on
end
#followed by a method for instantiating this class
I need filters :page_path.eql => to be the path of the page in which I call the method. I've tried things like request.request_uri or url_for(:action => 'show' :controllers => 'users' :id => params[:id]) but don't know how to specify the page path in this class definition.
This will break MVC encapsulation - I think you should be creating a before filter in your application_controller that passes the request data you need to your class.
EDIT
If you want to build a one-off report for a particular page, I think you'll need to do something like this:
profile = Garb::Profile.first('UA-XXXX-XX')
report = Garb::Report.new(profile)
report.metrics :pageviews
report.dimensions :page_path
report.filters :page_path.eql => request.request_uri
report.results
Again, if you're having this on every page, a before filter would be wise, I think. Pretty sure it's going to slow your app down a lot, though.
This is covered in the docs.

Resources