Rails 4 - Nested Resources - ruby-on-rails

User has_many Tickets.
Ticket belongs_to User (ticket.user_id)
routes.rb
resources :users do
resources :tickets
end
rake routes
user_tickets GET /users/:user_id/tickets(.:format) tickets#index
users/index.html.erb
<%= link_to("View User's Tickets", user_tickets_path(user)) %>
users_controller.rb
private
def set_user
#user = User.find(params[:id])
#tickets = #user.tickets
end
tickets_controller.rb
def index
#search = Ticket.search(params[:q])
#tickets = #search.result.paginate(:page => params[:page], :per_page => 25)
render 'shared/tickets.html.erb'
end
When I hover over link, it shows .../users/[the selected user's id]/tickets
When it goes to the ticket/index page, it shows ALL tickets, not just the tickets with the selected user's id.
I'm pretty sure my route is incorrect, or it may be something else entirely. Any help is appreciated.
EDIT
I think my problem is that I need to call #tickets in the tickets_controller/index method a variety of ways, because I want to use that view for #tickets.all, #user.tickets, #facility.tickets, etc (to keep it DRY).
The same index list of tickets needs to change, based on the link from whence it came (whether it comes from the user list, showing a list of all tickets by that user, or from the facility list, showing a list of all tickets by that facility). I'm just doing something horribly wrong somewhere.
Possible solution I will try:
Maybe I need to create custom routes, like get 'users_tickets' => "users#users_tickets", then put the #tickets conditions in that method and call the shared/tickets.html.erb that way.

Sounds like you need to step through the association. Did you use
tickets_controller.rb
def index
#user = User.find(params[:user_id])
#tickets = #user.tickets
end
in the controller? If this doesn't help, can you post the controller code?

Aren't you trying to whittle down #tickets rather than do another query?
Right now you're redefining #tickets when they hit the tickets index, and it doesn't care that you defined #tickets as just belonging to that user on the users_controller. It just ignores that because you're using direct assignment in your tickets_controller index action. You probably want something like:
tickets_controller.rb
def index
#search = Ticket.search(params[q])
#tickets = #search.result.where(user: #user)
#tickets = #tickets.paginate(:page => params[:page], :per_page => 25)
end
Not tested, but I think that's more what you're wanting.

Ok - I think I need to review nested resources again, because I thought they did what I wanted automatically...or maybe they do, and I just don't get it yet.
In any case, I ended up creating a custom route and custom method in users:
routes.rb
get 'user_tickets' => "users#user_tickets"
users_controller.rb
def user_tickets
#user = User.find(params[:id])
#search = Ticket.where(:user_id => #user.id).search(params[:q])
#tickets = #search.result.paginate(:page => params[:page], :per_page => 25)
render 'shared/tickets.html.erb'
end
then, called:
<%= link_to("View Tickets", user_tickets_path(:id => user.id)) %>
I will do the same for facilities, departments, etc. Not sure this is the best way, but it works.
Thanks everyone for stimulating my brain cells.

Related

Cancan Ability Issue with Inheritance

I'm having problems restricting the data shown to a specific user group using cancan..
My Users have many Products. And Products have many Vouchers.
In my routes.rb I have this:
resources :products do
resources :vouchers
end
In ability.rb:
can [:create, :update, :read ], Voucher, :product => { :user_id => user.id }
And in my Voucher controller:
def index
...
if params[:product_id]
#voucher = Voucher.find_all_by_product_id(params[:product_id])
end
...
end
Finally, in my view, I'm trying to display a list of vouchers in a Product group associated with current user.
For example:
http://localhost:3000/products/eef4e33116a7db/voucher
This lists the vouchers in the product group however, ALL users can see every voucher / product..
I'll assume my abilities are wrong. Help please :)
Have a look at the can can wiki for fetching records: https://github.com/ryanb/cancan/wiki/Fetching-Records
If you're calling load_and_authorize_resource, you'll either be able to do something similar to one of these two things:
def index
...
if params[:product_id]
#vouchers = #vouchers.where(product_id: params[:product_id])
end
...
end
load_and_authorize_resource should automatically assign the #vouchers instance variable based on the accessible_by parameters for that controller action. If this isn't working, just define it more explicitly:
if params[:product_id]
#vouchers = Voucher.includes(:product).where(product_id: params[:product_id]).where(product: { user_id: current_user.id })
end
For anyone else having this issue, I needed to do the following in my vouchers controller:
#product = Product.accessible_by(current_ability).find(params[:product_id])
#voucher = #product.vouchers
However, although this did actually block other users from viewing the results, loading the product first with accessible_by led to an exception which required a separate rescue block.

Username in Profile for Rails

Using RoR 2.3.8. I have the following codes:
user.rb
def to_param
"#{login.downcase.gsub(/[^[:alnum:]]/,'-')}".gsub(/-{2,}/,'-')
end
people_controller.rb
def show
#person = User.find(params[:id])
if current_user == #person
#posts = #person.posts.paginate(:page => params[:page], :order => order)
else
#posts = #person.posts.by_status('published').paginate(:page => params[:page], :order => order)
end
end
I have a column login in Users database where unique username is. People is just a controller to show some posts created by the user.
I will usually link to the index.html.erb under my people controller with the url http://localhost:3000/people/2 with the following code example in User's posts:
<%=h #post.user_name %>
I want the URL to be http://localhost:3000/people/victor where victor is the login for a user. This url should also actually show the profile show.html.erb in people controller.
What else do I need to do? Thanks!
I use the friendly_ID gem for this sort of thing - it's very straightforward - good luck
I would modify routes.rb, something like this:
match 'people/:login' => 'people#show', :as => 'login'
And then modify a people_controller.rb:
def show
#person = User.where(:login => params[:login]).first
end
edited after additional information
corrected error

Rails Polymorphic Associations plus Routes

I have a model, Report, that is polymorphic.
So many itens in my site may have many of it.
And i would like to have a generic controller for posting it.
Its a very simple model, has only a text message and the association.
in my routes, im doing something like
map.resources :users, :has_many => [ :reports ]
map.resources :posts, :has_many => [ :reports ]
but in my reports_controller, i would like to get the relation from with its coming from.
like:
before_filter :get_reportable
def get_reportable
reportable = *reportable_class*.find params[:reportable_id]
end
is this possible?
how could i get the reportable_class and the reportable_id?
I can get the params[:user_id] when it comes from users controller, or params[:post_id] when it comes from posts. I could do a case with all the relations, but it doesnt seem a clean solution at all...
having the polymorphic association would be the best, are there any how?
If you have a single controller that processes requests through two differing paths, then you need to make it aware of the contexts in which it will be called. You often see a lot of code that looks something like this:
before_filter :load_reportable
def load_reportable
if (params[:user_id])
#user = User.find(params[:user_id])
#reportable = #user
elsif (params[:post_id])
#post = Post.find(params[:post_id])
#reportable = #post
end
rescue ActiveRecord::RecordNotFound
render(:partial => 'not_found', :status => :not_found)
return false
end
Since you're using a polymorphic association, you may be able to do something like this instead:
before_filter :load_reportable
def load_reportable
unless (#reportable = #report.reportable)
# No parent record found
render(:partial => 'not_found', :status => :not_found)
return false
end
# Verify that the reportable relationship is expressed properly
# in the path.
if (params[:user_id])
unless (#reportable.to_param == params[:user_id])
render(:partial => 'user_not_found', :status => :not_found)
return false
end
elsif (params[:post_id])
unless (#reportable.to_param == params[:post_id])
render(:partial => 'post_not_found', :status => :not_found)
return false
end
end
end
The trouble with this approach, where you have one controller that serves two entirely different routes, is that generating error messages, such as "user not found" versus "post not found". This can be tricky to get right if you're not inheriting from a Users::BaseController, for instance.
In many cases it's easier to create two independent "reports" controllers, such as users/reports and posts/reports, where any common functionality is imported from a module. These controllers usually inherit from a base controller which performs the loading and error handling. The base controller can also establish layout, page title, etc., without having to re-implement this functionality for each sub-resources controller.
The alternative is to de-couple reports and have it run as its own controller where the relationship to the "reportable" record is mostly irrelevant.
Or try that:
before_filter :get_reportable
def get_reportable
params.each do |name, value|
if name =~ /(.+)_id$/
#reportable = $1.classify.constantize.find(value)
end
end
end
It is going through all the params and tries to find one ending with _id, then grabs that before part and finds relevant record.

Best Practice for views to nested resources in Rails?

I have a fairly simple model; Users have_many products. I would like to be able to view a list of all products as well as a list of the products associated with a given user. My routes are set up like this:
/products
/products/:id
/users
/users/:id
/users/:id/products
The catch here is that I'd like to display the product list differently in the product#index view and the user/products#index view.
Is there a 'correct' way to do this? My current solution is to define products as a nested resource inside users, and then to check for params[:user_id] - if its found I render a template called 'index_from_user', otherwise I just render the typical 'index' template.
This is a situation I'm running into a lot - if there's a preferred way to do it I'd love to know...
You can declare two "products" routes - one under users, and one independent of users eg:
map.resources :products
map.resources :users, :has_many => :products
They will both look for "ProductsController#index" but the second will have the "user_id" pre-populated from the route (note: "user_id" not just "id")
So you can test for that in the index method, and display different items depending on whether it is present.
You will need to add a before_filter to the ProductController to actually instantiate the user model before you can use it eg:
before_filter :get_user # put any exceptions here
def index
#products = #user.present? ? #user.products : Product.all
end
# all the other actions here...
# somewhere near the bottom...
private
def get_user
#user = User.find(params[:user_id])
end
If you really want to display completely different views, you can just do it explicitly in the index action eg:
def index
#products = #user.present? ? #user.products : Product.all
if #user.present?
return render(:action => :user_view) # or whatever...
end
# will render the default template...
end

How should I structure controller actions that share templates?

Let's say I have a User model, and an Invoice model with a belongs_to :user association.
Now I'm creating a new action for my InvoicesController, and the view that will be rendered. The view will have a select-element for selecting the user that this invoice will belong to.
So I need to fetch those users somewhere; my instinct is to leave this kind of thing out of the view. I end up with this:
def new
#users = User.all
end
The form submit action is create. When the creation fails for some reason, I re-render the new action's view.
def create
invoice = Invoice.new params[:invoice]
if invoice.save
flash[:notice] = 'Invoice created!'
redirect_to :action => 'show', :id => invoice.id
else
#users = User.all
render :action => 'new'
end
end
But as you can see, in order the re-render the new action, I have to fetch the users again.
This is just an example, but consider that I have some forms with several select-elements filled from the database, or similar constructs. That turns into an awful lot of repetition.
So how should I structure my controller in this situation?
Should I simply use User.all from my view?
Should I call new from within create to do the fetching for me?
Or something else?
For this I'd use a before_filter. For example you'd do something like:
before_filter :fetch_all_users, :only => [:new, :create]
protected
def fetch_all_users
#users = User.all
end
For 90% of my controllers I use the inherited resources plugin. It cuts down the amount of controller code you need to write for CRUD controllers, which also means you can cut down on the amount of tests you need to write.
For me:
What's the rails way to load other models collections for new, edit update and create actions?
It's not a good approach for my situation. Where after ".save", I send redirect_to to an another action, if I use before_filter and ".save" returns true, the fetch_all_users is called unnecessary

Resources