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
Related
How can I put model associations calls from views to controllers in Rails 4 or later?
For example I have:
Model:
class Parent
has_many :children
end
class Child
end
Controller:
class ParentController < ApplicationController
def index
#parents = Parent.all
end
end
class ChildrenController < ApplicationController
def index
#children = Parent.find(params[:parent_id]).children
end
end
View:
parents/index.html.erb
<% #parents.each do |parent| %>
<% render parent.children %>
<% end %>
some partial children/_child.html.erb
<%= child.name %>
route.rb
resources :parents do
resources :children
end
How can I substitute call of method parent.children by using somehow logic in controller - for example, ChildrenController::index method that requires url parameter?
I believe this will allow me to abstract view from model.
By abstraction I mean - if i change the model - for example, it won't have has_many association between parent and child - but still want to preserve the view then i will have only change the code in controller not in the view.
Further, i can extract the model logic in controller to some business class that will act like a model interface to use.
I am trying to build two-layered architecture with separated Presentation Layer (views, controllers) and Business layer (business classes, activerecords).
Probably this architecture could avoid the cases like with futurelearn portal that decided to split their STI model (single table) to several different ones. But having no real separation it became a creative task of tricking the Rails (see https://about.futurelearn.com/blog/refactoring-rails-sti). Code had a lot of association calls and it was necessary to develop smth to preserve these calls (only because otherwise it would be necessary to change too much code) and change model at the same time.
Not sure what you mean by
I believe this will allow me to abstract view from model.
You are not using model in the view per se. You are using the ActiveRecord associations in the view which I think is the right way to do this sort of thing. Not sure if rendering all the children for each parent is the right thing to do as far as reading all those records in memory is concerned but that's a whole different topic.
FWIW, both parent.children and Parent.find(params[:parent_id]).children will generate the exact same SQL on the backend.
That is ok, if you have such a call in your view, i believe that there is no good way to remove it. The only thing you can do - you can try to hide this call, but this will not bring you real abstraction.
But there are some things, that you need to do:
1. Add includes to load your associations in a single query
def index
#parents = Parent.includes(:children).all
end
2. If you don't need your parents in your view (it's not clear - do you only render children partial, or there are other lines in that view?), only children - you can just load your childrens :)
#children = Children.all
# or, if your children can be without parents
#children = Children.where.not(parent_id: nil).all
I think you missed put belongs_to:
class Parent
has_many :children
end
class Child
belongs_to :parent
end
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
In my view, I need a User object to display a few different properties. There is an instance variable #comments that's being sent from the controller. I loop through the comments and get the User information through a helper method in order to reduce db calls.
Here is the helper method:
def user(id)
if #user.blank? == false && id == #user.id
return #user
else
return #user = User.find(id)
end
end
And in the view, I display the details as follows:
<h4> <%=user(comment.user_id).name%> </h4>
<p><%=user(comment.user_id).bio%></p>
<p><%=user(comment.user_id).long_bio%></p>
<p><%=user(comment.user_id).email%></p>
<hr>
<p><%=user(comment.admin_id).bio%></p>
<p><%=user(comment.admin_id).long_bio%></p>
<p><%=user(comment.admin_id).email%></p>
I was told that assigning a variable in the view is bad practice and hence I am calling the helper method multiple times instead of assigning the returned User object.
Is there a better way to do this?
I think you are overcomplicating things here.
Let's say you have a user model
class User < ActiveRecord::Base
has_many :comments
end
an admin model
class Admin < User
end
a comment model
class Comment < ActiveRecord::Base
belongs_to :user
end
Now you only need a type column in your users table and you can do things like this:
Admin.all (All users with type "Admin")
User.all (Really all users including type "Admin" and all other types)
and for every comment you can just use
comment.user.bio
and it doesn't matter if it's an admin or not.
See http://www.therailworld.com/posts/18-Single-Table-Inheritance-with-Rails for example
Additional info: To reduce db calls in general(N+1 queries) watch http://railscasts.com/episodes/372-bullet
It's perfectly fine to pass models to your view and build the data on the view off of the data contained in the model. Keep in mind that I'm not entirely certain how you want your page to work, but one option you may have is to use a partial view and pass it the user object. This allows you to still only have the one model in your partial view without setting additional variables.
Also, without knowing what kind of database you're using or if your models have any associations, and assuming that you're doing some input validation, you may not need this helper method and may be able to lean on your ORM to get the user object.
For Example:
<%= comment.user.age %>
This isn't any more efficient than what you've currently got, but it certainly makes the code look cleaner.
Another alternative: set a user variable in the view. You're not performing logic in your view at this point, you're simply storing some data to the heap for later use.
I have the following line of code
<% map = options_for_select(User.all.map {|u| [u.first_name+" "+u.last_name, u.id]}) %>
which grabs the first and last name of a user and submits its ID in a form. Now I have added a few users and they are not in alphabetical order. How can I sort this map by first name?
You can also use order to get the rows already ordered from the database:
<% map = options_for_select(User.all.order(:first_name).map {|u| [u.first_name+" "+u.last_name, u.id]}) %>
May be...
<% map = options_for_select(User.all.map {|u| [u.first_name+" "+u.last_name, u.id]}.sort) %>
You should never use queries on the views. You should use views only for presentation, and all the logic on the Models and or the Controllers.
Also, respecting Fat Models, Skinny Controllers Best Practice:
In practice, this can require a range of different types of refactoring, but it all comes down to one idea: by moving any logic that isn’t about the response (for example, setting a flash message, or choosing whether to redirect or render a view) to the model (instead of the controller), not only have you promoted reuse where possible but you’ve also made it possible to test your code outside of the context of a request.
Finally, in this case it's best to use a scope to reuse it later.
Use a scope on User model, and have a name method:
class User < ActiveRecord::Base
scope :order_by_name, ->(first_name, last_name) { order("#{first_name} ASC, #{ last_name} ASC") }
def name
"#{first_name} #{last_name}"
end
end
If you're calling your line from users/index, create an instance variable to load the users collection like this:
class UsersController < ApplicationController
def index
#users = User.order_by_name
end
end
Then you would call on the view using options_from_collection_for_select like this:
<% map = options_from_collection_for_select(#users, :id, :name) %>
I recommend you to do both the CONCAT and ORDER in the Database for performance reasons
User.select("CONCAT(u.first_name, ' ', u.last_name), u.id").order("u.first_name")
This User.all.map could be a bottleneck of your application
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.