Rails, REST, which controller displays what? - ruby-on-rails

I have a controller which manages Users and a different one for Reservations.
Where should the code be located that displays all reservations for a specific user?
Is this done in the User controller or the Reservation controller?

It depends. If you're dealing with a nested resource (in other words you want to be able to access something like /users/4321/reservations), you should take a look at this Railscast, which is outdated for Rails 2 but still useful.
What you probably want is to have the code be in the ReservationsController under the index action, but it depends on what you're planning to do. Nonetheless, something like this would make sense:
class ReservationsController < ApplicationController
def index
#reservations = Reservations.where(user: params[:user_id])
end
end

I think you are slightly off base here... your models should have an association; e.g.:
User < ActiveRecord::Base
has_many :reservations
end
Your UserController#show might be
def show
#user = User.find(params["id")
end
And then the view would have something like:
<ul>
<% #user.reservations.each do |r| %>
<li><%= r.details =%></li>
<% end %>
</ul>
The association opens it up so that you can call the needed object from either. You could just as easily show #reservation.user.name in the Reservation view

Where should the code be located that displays all reservations for a specific user?
It depends. If you want to show reservations for a specific user on his detail page (/users/42), then UsersController#show action is perfectly reasonable. If you want to make a search form, then it's basically a filter on the ReservationsControler#index action.

Related

Ruby on Rails 4 find object by id

I have the following show-view, where i display basic information about Product and display other User's Products.
<h1>Book <%= #product.name %></h1>
<% #products.each do |product| %>
<ul>
<%= product.name %>
<%= link_to "Make offer", {controller: "offers", :action => 'create', id: product.id } %>
</ul>
Controller
def show
#product = current_user.products.find(params[:id])
#products = Product.all
end
My goal is to make Offer between two Products.
I created Offer model and methods for making Offers:
class Offer < ActiveRecord::Base
belongs_to :product
belongs_to :exchanger, class_name: "Product", foreign_key: "exchanger_id"
validates :product_id, :exchanger_id, presence: true
def self.request(product, exchanger)
unless product == exchanger or Offer.exists?(product, exchanger)
transaction do
create(product: product, exchanger: exchanger, status: "oczekujace")
create(product: exchanger, exchanger: product, status: "oferta")
end
end
#other methods
end
Making offers is working, because I checked it in Console.
My problem is in OffersController:
class OffersController < ApplicationController
before_filter :setup_products
def create
Offer.request(#prod, #exchanger)
redirect_to root_path
end
private
def setup_products
#prod = current_user.products.find(1)
#exchanger = Product.find_by_id(params[:id])
end
end
Problem is with a following line (using link in show-page for products with different id's than 1 works):
#prod = current_user.products.find(1)
But I don't know how to find object in Db for actual product which my show-page shows. Not only for id = 1.
I don't know how to find this object in database.
I don't know the specific answer to your question, but perhaps if I explain what you need to look at, your solution will arise:
Find
Rails isn't magic - it uses ActiveRecord (which is an ORM - Object-Relation Mapper), which means every time you fire a query (through find), your ORM (ActiveRecord) will search the relevant database data for you
The problem you have is that although you're using the correct syntax for your lookup, you may not have a record with an id of 1 in your db.
current_user.products.find(1) tells ActiveRecord to scope the query around the current user, and their products. So you'll get something like like this:
SELECT * FROM 'products' WHERE user_id = '15' AND id = '1'
Objects
Further, you have to remember that Ruby (and Rails by virtue of being built on Ruby) is an object orientated language. This means that everything you load / interact with in the language should be based on an object
The problem you have is you're not associating your object to your Rails framework correctly. What I mean here is described below, but essentially, if you build your Rails framework correctly, it will give you the ability to associate your objects with each other, allowing you to call the various products you need from your offer
This is a simplistic way of looking at it, of course. You'll want to look at this diagram to see how it works:
Bottom line - try treating your application like a series of objects, rather than a logical flow. This will help you appreciate the various associations etc that you need to get it moving forward
Resources
You mention you can't show the product on your show page for an id other than one. I think the problem is really about how to get your show action to work.
If this is the case, let me explain...
Rails is resource-based, meaning that everything you do / create needs to be centred around a resource (object) of some sort. The problem is many people don't know this, and consequently complicate their controller structure for no reason:
Above is the typical "CRUD" routing structure for Rails-based resources. This should demonstrate the way that Rails will typically be constructed -- around resources
--
Further, Rails is built on the MVC programming pattern - meaning you need to use your controller to populate a series of data objects for use in your application.
To this end, if you load a resource, and want to populate it with resourceful information of another object - you need to make sure you have set up the data objects in a way to ensure you can look them up correctly, which either means passing the data through your routes or using a persistent data-type, such as cookies or sessions
The problem you have is you need to pass the product id to your controller somehow. How I'd do that is as follows (using nested resources):
#config/routes.rb
resources :offers do
resources :products #-> domain.com/offers/2/products
end
This will give you the ability to load the products controller with the variables params[:id] for the product, and params[:offer_id] for your Offer made available:
#app/controllers/products_controller.rb
Class ProductsController < ApplicationController
def show
#offer = Offer.find params[:offer_id]
#product = Product.find params[:id]
end
end

Rendering query best practice rails 4.0

I have a model for Contracts that has a field called "totalAward". In my index.html.erb view I have the following code:
<p> Total Award Amount: <td><%= number_to_currency(Contract.sum('awardAmount')) %></td> </p>
I'm fairly certain it's not a best practice for me to do a database call in the view, but I am not sure how to do it in the controller or the model in a way that I can render it in the view. Could someone help me with how make that database call in the controller or the model?
Thanks!
The instance variable (#contracts) from your controller will give you access to the attributes of your model. I believe you want to use ActiveRecord's sum method. Here is how you can use it in your view:
<%= #contracts.sum(:totalAward) %>
This way, you are not querying the db in your views. but you have access to the instance variable (#contracts) in your controller which holds a collection of all of your contracts.
Further to Wali Ali, you will be best sticking to the MVC programming pattern (the basis of Rails). This means you need to keep your data-allocation (set variables) in your controller, and data storage in your models
I would use a instance method:
#app/models/contract.rb
Class Contract < ActiveRecord::Base
def total
sum(:totalAward)
end
end
This will allow you to perform things like this:
#app/controllers/contracts_controller.rb
Class ContractsController < ApplicationController
def index
#contracts = Contract.all #-> #contracts.total for all
#contracts = Contract.where(some: value) #-> #contracts.total for these items
end
end

Finding user object in view? Ruby on Rails

<% #review.each do |review|%>
<% if review.host_id == #host.id>
<%= #user = User.find(review.user_id) %>
<% end %>
<% end %>
So I'm a bit confused. I have a few things going on here. I'm doing a loop through all reviews of hosts and then checking if the stored host.id value is equal to the active #host object's id that is passed from the controller. Problem is.. Now I need get the user object from the user ID stored in the review but, I'm unsure exactly how to do it. I can't do it from the controller as all this is done in the loop. As you can see I tried to do it with the code above but, I highly doubt I did it right. Please help me out on this. Thanks.
You should pre-load users with loading reviews, in controller. First, you should have belongs_to association, like this:
class Review < ActiveRecord::Base
belongs_to :user
# ...
end
then, in controller, you could use includes, this way:
#reviews = Review.includes(:user)
Now, for every review record in #reviews relation, to get associated user you can call user method, like this:
review.user
What's more, (and that's advantage of using includes) it doesn't fire new SQL query for every single review, so you avoid quite common N + 1 problem.
You can make a relationship in
class Review < ActiveRecord::Base
belongs_to :user
end
and then in view
review.user #gives you user
Put association in Review Model
class Review < ActiveRecord::Base
.
.
.
belongs_to :user
.
.
.
end
After putting association you can directly call association to find user object using Review object.
review.user
But this will raise N+1 query problem, so better user include user while finding review, It will execute only two queries one for finding reviews and another for finding users.
#reviews = Review.includes(:user)

Rails routing/controllers - listing subsets of collections

I have a blog with posts in multiple categories. I'd like to give each category an individual landing page that lists all of the bog posts in that category.
What is the appropriate way to generate the routes and controller actions for each of these landing pages? Would it violate the spirit of REST to create multiple index-esque actions (one action per category) in my posts controller? If so, how else should I do it?
For example, my blog might have two categories, "Music" and "Movies".
GET /posts/ # would list all posts.
GET /music/ # would list all posts in the "Music" category.
GET /movies/ # would list all posts in the "Movies" category.
Apologies if this question has an obvious answer, or if I'm asking the wrong question entirely. I'm new to both Rails and REST and I'm trying to understand the best way to structure applications.
I'm not sure it's totally in REST spirit (i do not fully understand it yet), so i'll leave that part of the question to someone else. As the collection method exists to extend RESTful routes, i assume that it is permitted as long as you don't abuse of it.
I don't think, though, that having routes with no "/posts/" prefix is a good thing, because it would induce that the "/music/" path for instance relates to a completely different resource.
you can do something like this :
(in routes.rb)
resources :posts do
collection do
get 'music'
get 'movies'
end
end
... and then add index-like actions to your controller, e.g.:
def music
#posts = Post.where( category: 'music')
render :index
end
if you have a limited and constant set of categories, this can be DRYed up this way:
class Post < ActiveRecord::Base
CATEGORIES = [:music,:movies,:art,:jokes,:friends,:whatever].freeze
end
class PostsController < ApplicationController
Post::CATEGORIES.each do |category|
eval <<-INDEX_LIKE_ACTIONS
def #{category}
#posts = Post.where( category: '#{category}' )
render :index
end
INDEX_LIKE_ACTIONS
end
end
resources :posts do
collection do
Post::CATEGORIES.each {|category| get category.to_s}
end
end

Best practice: How to split up associations-functions in controllers with equal-access models

I have 2 equal-access models: Users and Categories
Each of these should have the standard-actions: index, new, create, edit, update and destroy
But where do I integrate the associations, when I want to create an association between this two models?
Do I have to write 2 times nearly the same code:
class UsersController << ApplicationController
# blabla
def addCategory
User.find(params[:id]).categories << Category.find(params[:user_id])
end
end
class CategoriessController << ApplicationController
# blabla
def addUser
Category.find(params[:id]).users << User.find(params[:user_id])
end
end
Or should I create a new Controller, named UsersCategoriesController?
Whats the best practice here? The above example doens't look very DRY.... And a new controller is a little bit too much, I think?
Thanks!
EDIT:
I need to have both of these associations-adding-functions, because f.e.
#on the
show_category_path(1)
# I want to see all assigned users (with possibility to assign new users)
and
#on the
show_user_path(1)
#I want to see all assigned categories (with possibility to assign new categories)
EDIT:
I'm taking about a HBTM relationship.
If you have a situation where you need to do this with has_and_belongs_to_many, you could take the approach you are currently using, or you could build this into your existing update actions.
When you add a habtm relationship, you will get an additional method on your classes...
class User < ActiveRecord::Base
has_and_belongs_to_many :categories
end
With this, you can do this:
user = User.find(params[:id])
user.category_ids = [1,3,4,7,10]
user.save
The categories with those ids will be set. If you name your form fields appropriately, the update can take care of this for you if you want to use checkboxes or multiselect controls.
If you need to add them one at a time, then the methods you've built in your original post are reasonable enough. If you think the repetition you have is a code smell, you are correct - this is why you should use the approach I outlined in my previous answer - an additional model and an additional controller.
You didn't mention if you are using has_and_belongs_to_many or if you are using has_many :through. I recommend has_many :through, which forces you to use an actual model for the join, something like UserCategory or Categorization something like that. Then you just make a new controller to handle creation of that.
You will want to pass the user and category as parameters to the create action of this controller.
Your form...
<% form_tag categorizations_path(:category_id => #category.id), :method => :post do %>
<%=text_field_tag "user_id" %>
<%=submit_tag "Add user" %>
<% end %>
Your controller...
class CategorizationsController < ApplicationController
def create
if Categorization.add_user_to_category(params[:user_id], params[:category_id])
...
end
end
then your categorization class...
class Categorization
belongs_to :user
belongs_to :category
def self.add_user_to_category(user_id, category_id)
# might want to validate that this user and category exist somehow
Categorization.new(:user_id => user_id, :category_id => category_id)
Categorization.save
end
end
The problem comes in when you want to send the users back, but that's not terribly hard - detect where they came from and send them back there. Or put the return page into a hidden field on your form.
Hope that helps.

Resources