I am developing a rails app and I have 2 different user's role: advanced and basic.
Instead of to hide links in the basic user's views (a.i. using CanCan ) I want to manage 2 different set of views: one for the advanced user and one for basic user.
Currently I am working in this way:
case current_operator.op_type
when 'basic'
format.html { render :template => "devices/index_basc.html.erb" }
when 'advanced'
format.html # index.html.erb
end
But I dont like to specify at every action the template for the basic user ( { render :template => "devices/index_basc.html.erb" } )
I think there is some other way (I hope more neat :)
Do you have any ideas ?
Thank you,
Alessandro
You can do something like in this Railscast Mobile Devices:
in config/initializers/mime_types.rb add:
Mime::Type.register_alias "text/html", :basic
in app/controllers/application_controller.rb add:
before_filter :check_user_status
private
def check_user_status
request.format = :basic if current_operator.op_type == 'basic'
end
Now you can just do the following in your controllers:
class SomeController < ApplicationController
def index
# …
respond_to do |format|
format.html # index.html.erb
format.basic # index.basic.erb
end
end
end
As you have only two different user's role you can do this
page = (current_operator.op_type =='basic')? "devices/index_basc.html.erb" : "index.html.erb"
format.html { render :template => page}
Related
I have a Deals Rails app showing the list of products on home page.
Deals have a deal_country attribute.
My goal: I'd like to apply a before filter to filter the list of deals displayed to those whose deal.deal_country is the same as user's country (obtained through ip detection)
I tried the following but it does not work:
class StaticPagesController < ApplicationController
before_filter :filter_deals_based_on_visitor_country,
:only => [ :home]
def filter_deals_based_on_visitor_country
visitor_country = request.location.country
#deal = Deal.find_by(:deal_country => visitor_country)
end
def home
#deals = Deal.featured_on_homepage.to_a # deals that are now active given their start date
respond_to do |format|
format.html # home.html.erb
format.json { render json: #deals }
format.xml { render :xml => #deals }
# format.atom
end
end
end
I am not getting any error but it's not "filtering"/displaying deals based on user's country. It does nothing different as if I had not put anything more than the before filters. method.
How can I do this?
EDIT
To be sure it's not working I have created deals that are taking place in the US with deal.deal_country= US". But in local, i am seen as being in FRance and I can still see it.
Note: I have put to have in local /dev mode my French ip by doing this (i am sure it'w sorking because when i put in the homepage view: <%= Geocoder.search(request.remote_ip).first.country %> it displays France)
in application_controller.rb
def lookup_ip_location
if Rails.env.development?
Geocoder.search(request.remote_ip).first
else
request.location
end
end
in config/envrionment/development.rb
class ActionDispatch::Request
def remote_ip
"84.34.156.155" # fake ip for example
end
end
models/deal.rb
scope :featured_on_hp, lambda { default.where('date_launch_date <= ? AND date_end_date >= ?', Time.zone.now, Time.zone.now).where(featured: true) }
you forgot ! in def filter_deals_based_on_visitor_country, so its not getting called.
even if you correct this mistake, you will get an error in this line
#deal = Deal(:deal_country => visitor_country)
the correct syntax is
#deal = Deal.find_by(:deal_country => visitor_country)
We have a requirement where a user needs to select their avatar for their profile. On the edit profile page, the user clicks on a Change Picture link which takes them to another page and gives them with two links to get their photo from facebook or gravatar. There is also a preview of the image shown on this page, as well as a save button. The controller for this page is AvatarsController. I have edit and update actions, as well as custom GET actions for facebook and gravatar, so that the route looks like avatar/facebook, and avatar/gravatar. These actions simply query the respective services and create a new avatar model containing the url for the photo. When the user clicks save, the update action is called and the avatar model is saved with the profile. The page is delivered by the edit template, as by default, when a user is created, an empty avatar is also created.
The Profile model (using mongoid) essentially looks like:
def Profile
embeds_one :avatar
end
and the avatar model looks like:
def Avatar
embedded_in :profile
end
The route looks like:
resource :avatar, only: [:edit, :update] do
member do
get 'facebook'
get 'gravatar'
end
end
The controller looks like:
class AvatarsController < ApplicationController
def facebook
url = AvatarServices.facebook(current_user, params[:code])
respond_to do |format|
unless url
format.json { head :no_content }
else
#avatar = Avatar.new({:url => url, :source => "Facebook"})
#avatar.member_profile = current_user.member_profile
format.html { render :edit }
format.json { render json: #avatar }
end
end
end
def gravatar
respond_to do |format|
url = AvatarServices.gravatar(current_user)
unless url
format.json { head :no_content }
else
#avatar = Avatar.new({:url => url, :source => "Gravatar"})
#avatar.member_profile = current_user.member_profile
format.html { render :edit }
format.json { render json: #avatar }
end
end
end
def edit
#avatar = current_user.member_profile.avatar
end
def update
#avatar = current_user.member_profile.avatar
respond_to do |format|
if #avatar.update_attributes(params[:avatar])
format.html { redirect_to edit_member_profile_path }
format.json { head :no_content }
else
format.html
format.json { render json: #avatar.errors }
end
end
end
end
This works, but being fairly new to rails, I'm wondering if rails experts would have set up the 'facebook' and 'gravatar' resources differently, perhaps in a more RESTful manner?
Well, the subfolder is putting the facebook and gravatar controllers into a common namespace. You could use nested routes,
resource :avatar, only: [:edit, :update] do
resource :facebook
resource :gravatar
end
This will route to a FacebooksController and a GravatarsController.
This is kind of what you were thinking anyway, and you won't need a record id for a facebook or gravatar record.
Could you add your controller code? I'm interested to see how you have your actions setup.
If you want to keep things restful, it might just be a matter of creating a controller subfolder for avatars, and created subsequent controllers for gravatar & facebook. You can do this just using a generator
rails g controller avatars/facebook
rails g controller avatars/gravatar
Suppose you want a Blog with two different layouts. One layout should look like a conventional Blog with a header, a footer, a menu and so on. The other layout should only contain the blog posts and nothing more. How would you do that without losing the connection to the model, forcing the execution and rendering of only one action and prevent to repeat yourself (DRY)?
posts_controller.rb
class PostsController < ApplicationController
layout :choose_layout
# chooses the layout by action name
# problem: it forces us to use more than one action
def choose_layout
if action_name == 'diashow'
return 'diashow'
else
return 'application'
end
end
# the one and only action
def index
#posts = Post.all
#number_posts = Post.count
#timer_sec = 5
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
# the unwanted action
# it should execute and render the index action
def diashow
index # no sense cuz of no index-view rendering
#render :action => "index" # doesn't get the model information
end
[..]
end
Possibly I want to go the wrong way, but I can't find the right one.
Update:
My solution looks like this:
posts_controller.rb
class PostsController < ApplicationController
layout :choose_layout
def choose_layout
current_uri = request.env['PATH_INFO']
if current_uri.include?('diashow')
return 'diashow'
else
return 'application'
end
end
def index
#posts = Post.all
#number_posts = Post.count
#timer_sec = 5
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
[..]
end
config/routes.rb
Wpr::Application.routes.draw do
root :to => 'posts#index'
match 'diashow' => 'posts#index'
[..]
end
Two different routes are pointing at the same location (controller/action).
current_uri = request.env['PATH_INFO'] saves the url into a variable and the following if current_uri.include?('diashow') checks if it is the route we configured in our routes.rb.
You would select which layout to render depending on a certain condition. For example, a parameter in the URL, the device in which the page is being rendered etc.
Just use that condition in your choose_layout function, instead of deciding the layout on the basis of action_name. The diashow action is unnecessary.
I am using Devise for authentication, and I only need a simple admin or use check for a few controllers. I'm new to rails, so I'm trying to do this the right way. I've basically added a boolean admin field to the user model and added this method
def is_admin?
admin == 1
end
Then I simply modified the controller action to this
def new
if current_user.nil? || !current_user.is_admin?
flash[:notice] = "You do not have permission to view this page"
redirect_to "/gyms"
else
#gym = Gym.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #gym }
end
end
end
So this solution works, but should I be doing this a different way?
This will work but I probably would not recommend this solution for anything else than a small scale project. Over time, if you perform authorization checks within your controllers, your code is going to become bloated and difficult to manage.
Instead I would consider using an authorization module such as Cancan which centralizes your authorization rules in one place and thus decouples your application logic from your authorization logic. The end result is cleaner and more maintainable code.
With Cancan in place, your code might look like this:
# app/controllers/gyms_controller.rb
class GymsController < ApplicationController
load_and_autorize_resource
def new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #gym }
end
end
end
end
# app/models/Ability.rb
can :create, Gym do |trip|
user.is_admin?
end
I want to explicitly call a view from my controller.
Right now I have:
def some_action
.. do something ...
respond_to do |format|
format.xml
end
end
... then it calls my some_action.xml.builder view. How can I call some other view? Is there a parameter in respond_to I'm missing?
Thanks,
JP
You could do something like the following using render:
respond_to do |format|
format.html { render :template => "weblog/show" }
end
See the Rendering section of the ActionController::Base documentation for the different ways you can control what to render.
You can tell Rails to render a specific view (template) like this:
# Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.erb)
render :template => "weblog/show"
# Renders the template with a local variable
render :template => "weblog/show", :locals => {:customer => Customer.new}
Or even simpler since Rails > 3.0:
render "edit"
You can also pass :action, or :controller if that's more convenient.
respond_to do |format|
format.html { render :action => 'show' }
end
You can modify the internal lookup_context of the controller by doing this in your controller
before_filter do
lookup_context.prefixes << 'view_prefix'
end
and the controller will try to load view/view_prefix/show.html when responding to an show request after looking for all the other view prefixes in the list. The default list is typically application and the name of the current controller.
class MagicController
before_filter do
lookup_context.prefixes << 'secondary'
end
def show
# ...
end
end
app.get '/magic/1`
This GET request will look for a view in the following order:
view/application/show.erb
view/magic/show.erb
view/secondary/show.erb
and use the first found view.
Use render
http://api.rubyonrails.com/classes/ActionController/Base.html#M000474