How to create a rails api that routes different urls - ruby-on-rails

Am new to rails I want to perform a get request in different urls
such as
http://website.com/api/example1
http://website.com/api/example2
http://website.com/api/example3
http://website.com/api/example4
And how can i set session data to be accessed across all controllers in the app

You can simply set your session value in your code as so:
session[:some_value] = 'This is a session value'
What you do not want to do is store large amounts of data into your session. This will have huge performance implications. If you need to store large amounts of data, I would suggest a model to store it in within the database.
Although, I would question why you are setting things in the session for URL access?
http://guides.rubyonrails.org/security.html#sessions

You can add something like following at your routes.rb:
scope "api",:module => "api" do
get 'example1' => 'exact_data#example1', :as => 'api_example1'
get 'example2' => 'exact_data#example2', :as => 'api_example2'
get 'example3' => 'exact_data#example3', :as => 'api_example3'
end
So your controller should have:
class Api::ExactDataController < ApplicationController
def example1
#...
end
def example2
#...
end
def example3
#...
end
end
If you have common session logic that you want to access and use from all controllers then you can put them at your application_controller.rb as by default it is parent of all other controllers that you create.
For example if you put a current_user method at ApplicationController then you would have current_user method in all your controllers:
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
#current_user ||= User.find_by_username(session[:username]) if session[:username]
end
end

Related

How to modify cancan load and authorize resource to load resource using different id

How can I modify load and authorize resources to load resource using different id. for ex.
my routes is
http://localhost:3000/organization/user/12/event/20/edit
and in my event controller I am accessing event using :event_id and user using :id
Now in my event controller how can I modify load_and_authorize_resource use :event_id instead of :id for loading an event
The resource will only be loaded into an instance variable if it hasn't been already. This allows you to easily override how the loading happens in a separate before_filter.
class EventsController < ApplicationController
before_filter :find_event
load_and_authorize_resource
private
def find_event
#event = Event.find(params[:event_id])
end
end
Read more in the docs: https://github.com/CanCanCommunity/cancancan/wiki/Authorizing-controller-actions
CanCan supports this through the id_param option:
From the documentation:
# [:+id_param+]
# Find using a param key other than :id. For example:
#
# load_resource :id_param => :url # will use find(params[:url])
Therefore you can do the following in your situation:
load_and_authorize_resource :id_param => :event_id

Rails file setup for a control panel?

How do you setup your views, controllers and routes?
One controller for everything the control panel does, or many?
Firstly, let's try to think how we would view the various panels. Let's say our control panel is pretty simple. We have one panel to show all the users who have signed-up and can CRUD them, and another panel to show all of the images that have uploaded, and we can carry up CRUD on those too.
Routes:
scope path: 'control_panel' do
get 'users', to: 'panels#users', as: :panel_show_users
get 'photos', to: 'panels#photos', as: :panel_show_photos
end
Controller:
class PanelsController < ApplicationController
def users
#users = User.all
end
def photos
#photos = Photo.all
end
end
View file structure:
panels
|_ users.html.erb
|_ photos.html.erb
Okay, now I don't see any problems with that, to simply access the panels and populate the views with data. Do you see any problems?
Here is where I'm sort of at a cross roads though. What should I do when I want to Created Update and Delete a user/photo? Should I put them all in the PanelsController?
class PanelsController < ApplicationController
before_action :protect
def users
#users = User.all
end
def update_user
#user = User.find(params[:id])
#user.update(user_params)
end
def photos
#photos = Photo.all
end
def update_photo
#photo = Photo.find(params[:id])
#photo.update(photo_params)
end
private
def protect
redirect_to root_url, error: 'You lack privileges!'
end
end
While this would result in a large PanelsController, it would feel good to be able to execute that protect action and just one controller hook. It would also mean the routes would be easy to setup:
scope path: 'control_panel' do
get 'users', to: 'panels#users', as: :panel_show_users
post 'user', to: 'panels#update', as: :panel_create_user
get 'photos', to: 'panels#photos', as: :panel_show_photos
post 'photos', to: 'panels#photos', as: :panel_create_photo
end
I should use resource routes here?
Like I say, this will result in a huge panels controller, so I was thinking it may be better to have a separate controller for each resource and then redirect to panels views?
Routes:
scope path: 'control_panel' do
resources :users
resources :photos
end
Controllers:
class UsersController < ApplicationController
def index
end
def show
end
def new
end
def create
end
def update
end
def destroy
end
end
class PhotosController < ApplicationController
def index
end
def show
end
def new
end
def create
end
def update
end
def destroy
end
end
Still some quirks though. I have my Users#index action there, but what if I have two routes that return an index of all users? In the control panel, but also, when people are searching for another user, for example. Should I have two routes in the User controller? def public_users and def control_panel_users? That may be the answer. Could setup a hook to run #users = User.all in both of them, but redirect to a different location, and not have the protect method redirect them.
How should I protect these routes from non-admins? Should I move my protect method into the the application controller? Wouldn't this be a bit fiddly to setup?
class ApplicationController < ActionController
before_action :protect
def protect end
end
class StaticController < ApplicationController
skip_before_action [:home, :about, :contact]
def home
end
def about
end
def contact
end
end
But that is my question. 1 control panel controller or no control panel controller.
I really wish there was more advanced tutorials out there :( Billions of books on CRUD, MVC and things, but nothing on advanced things like control panels and AJAX...
Don't have a control panel controller. And to protect stuff from non-admins, use namespacing - read more about it here: http://guides.rubyonrails.org/routing.html#controller-namespaces-and-routing
You can protect your 'admin'-namespaced controllers with authentication, and have the non-namespaced controllers open to the public (or open to non-admin users)
With regards to your def public_users and def control_panel_users question, you could just have two def index methods - one in the non-namespaced controller, and one in the admin-namespaced controller. They would each do different things.
So, you'd have 4 controllers in total:
2 non-namespaced, one for users, one for photos (containing all public stuff)
2 admin-namespaced, one for users, one for photos (containing all control panel stuff)
If you wanted, rather than using 'admin' as the namespace, you could use some other term you prefer - like 'panel'. 'Admin' is pretty conventional though.

Owner-filtered model objects on Rails 3

I need to do some filtering on my ActiveRecord models, I want to filter all my model objects by owner_id. The thing I need is basically the default_scope for ActiveRecord.
But I need to filter by a session variable, which is not accessible from the model. I've read some solutions, but none works, basically any of them says that you can use session when declaring default_scope.
This is my declaration for the scope:
class MyModel < ActiveRecord::Base
default_scope { where(:owner_id => session[:user_id]) }
...
end
Simple, right?. But it fails saying that method session does not exists.
Hope you can help
Session objects in the Model are considered bad practice, instead you should add a class attribute to the User class, which you set in an around_filter in your ApplicationController, based on the current_user
class User < ActiveRecord::Base
#same as below, but not thread safe
cattr_accessible :current_id
#OR
#this is thread safe
def self.current_id=(id)
Thread.current[:client_id] = id
end
def self.current_id
Thread.current[:client_id]
end
end
and in your ApplicationController do:
class ApplicationController < ActionController::Base
around_filter :scope_current_user
def :scope_current_user
User.current_id = current_user.id
yield
ensure
#avoids issues when an exception is raised, to clear the current_id
User.current_id = nil
end
end
And now in your MyModel you can do the following:
default_scope where( owner_id: User.current_id ) #notice you access the current_id as a class attribute
You will not be able to incorporate this into a default_scope. This would break every usage within (e.g.) the console as there is no session.
What you could do: Add a method do your ApplicationController like this
class ApplicationController
...
def my_models
Model.where(:owner_id => session[:user_id])
end
...
# Optional, for usage within your views:
helper_method :my_models
end
This method will return a scope anyhow.
Session related filtering is a UI task, so it has its place in the controller. (The model classes do not have access to the request cycle, session, cookies, etc).
What you want is
# my_model_controller.rb
before_filter :retrieve_owner_my_models, only => [:index] # action names which need this filtered retrieval
def retrieve_owner_my_models
#my_models ||= MyModel.where(:owner_id => session[:user_id])
end
Since filtering by ownership of current user is a typical scenario, maybe you could consider using standard solutions, like search 'cancan gem, accessible_by'
Also be aware of the evils of default_scope. rails3 default_scope, and default column value in migration

Trouble on caching an action when I rewrite a URL

I am using Ruby on Rails 3 and I have an issue on caching when I rewrite a URL in the model using the to_param method.
In my User model I have:
class User < ActiveRecord::Base
def to_param # Rewrites URL
"#{self.id}-#{self.name}-#{self.surname}"
end
...
end
In the User controller I have:
class UsersController < ApplicationController
caches_action :show
def show
...
end
end
In the Users sweeper I have:
class UsersSweeper < ActionController::Caching::Sweeper
observe User
def after_save(user)
clear_users_cache(user)
end
def after_destroy(user)
clear_users_cache(user)
end
def clear_users_cache(user)
expire_action :controller => :users, :action => :show, :id => user
end
end
Now, when I browse the user show page in the log file I get:
Write fragment views/<my_site_name>/users/2-Test_name-Test_surname (0.3ms)
When I expire the cache after a change the name or surname in the log file I get
Expire fragment views/<my_site_name>/users/2-New_test_name-New_test_surname (0.3ms)
So, since the data is changed, it doesn't expire the cache because Rails try to expire 2-New_test_name-New_test_surname and not 2-Test_name-Test_surname.
How can I "easly" handle the Rails caching behavior to make it to work?
P.S.: Of course if I don't use the to_param method, it works as well.
UPDATED
I can do something like this
caches_action :show, :cache_path => Proc.new { |c| 'users/' + c.params[:id].split('-').first }
but I don't think that is a good way to solve things...
Try using a custom path:
You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
caches_action :show, :cache_path => { :project => 1 }
Obviously customize to suit your needs. See the API for more info.

Where do I put 'helper' methods?

In my Ruby on Rails app, I've got:
class AdminController < ApplicationController
def create
if request.post? and params[:role_data]
parse_role_data(params[:role_data])
end
end
end
and also
module AdminHelper
def parse_role_data(roledata)
...
end
end
Yet I get an error saying parse_role_data is not defined. What am I doing wrong?
Helpers are mostly used for complex output-related tasks, like making a HTML table for calendar out of a list of dates. Anything related to the business rules like parsing a file should go in the associated model, a possible example below:
class Admin < ActiveRecord::Base
def self.parse_role_data(roledata)
...
end
end
#Call in your controller like this
Admin.parse_role_data(roledata)
Also look into using (RESTful routes or the :conditions option)[http://api.rubyonrails.org/classes/ActionController/Routing.html] when making routes, instead of checking for request.post? in your controller.
Shouldn't you be accessing the parse_role_data through the AdminHelper?
Update 1: check this
http://www.johnyerhot.com/2008/01/10/rails-using-helpers-in-you-controller/
From the looks of if you're trying to create a UI for adding roles to users. I'm going to assume you have a UsersController already, so I would suggest adding a Role model and a RolesController. In your routes.rb you'd do something like:
map.resources :users do |u|
u.resources :roles
end
This will allow you to have a route like:
/users/3/roles
In your RolesController you'd do something like:
def create
#user = User.find_by_username(params[:user_id])
#role = #user.roles.build(params[:role])
if #role.valid?
#role.save!
redirect_to #user
else
render :action => 'new'
end
end
This will take the role params data from the form displayed in the new action and create a new role model for this user. Hopefully this is a good starting point for you.

Resources