Should I save a Model calculation as an attribute? - ruby-on-rails

I have an app that enables a user to create a new room using a form to enter name, description, length, and width. Each room created becomes a new record and the app calculates the 'size' of the room as 'length' * 'width'. This is a simple app that I'm playing with to learn Rails but I may take it further to have a collection of rooms form a house, with some total 'size' of each house.
My question relates to the 'size' value and how that should be integrated into the app. I initially thought that the user should see the value of 'size' right away on the form, but shelved that once it appeared that Ajax may be required. I moved the 'size' method calculation from the view to the model to conform to "fat model, skinny controller" concept and I now show the 'size' in the 'index' view, leaving the 'new' view purely for entering data.
I initially set up the model to include length, width and size. See migration for the Room model:
20150118183743_create_rooms.rb
class CreateRooms < ActiveRecord::Migration
def change
create_table :rooms do |t|
t.string :name
t.text :description
t.integer :length
t.integer :width
t.integer :size
t.timestamps null: false
end
end
end
Should I save 'size' for each record to the database? I've read that it shouldn't be necessary to save calculations as an attribute to the model. Presumably, the app should handle that? What's the correct way to think about this?
My 'index' view calculates & returns max 'length' and 'width', but I run into an error when I try to calculate the max 'size'. I have a calculation (i.e., method) for this in model but it appears to be wrong. Any suggestions?
Below are relevant code:
room.rb
class Room < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 30 },
uniqueness: { case_sensitive: false }
validates :length, :width, presence: true,
numericality: { only_integer: true,
less_than_or_equal_to: 1000,
greater_than_or_equal_to: 1 }
def size
size = length * width
end
def max_room
size.max
end
end
rooms_controller.rb
class RoomsController < ApplicationController
def show
#room = Room.find(params[:id])
end
def new
#room = Room.new
end
def index
#rooms = Room.all
end
def create
#room = Room.new(user_params)
if #room.save #a boolean, if able to save the instance
flash[:success] = "You created a new Room!!"
redirect_to #room #we send the user to the room
else
render 'new' #so we want to render the new template
end
end
private
def user_params
params.require(:room).permit(:name, :description, :length,
:width, :size)
end
end
index.html.erb
<% provide(:title, 'All Rooms') %>
<h1>All rooms</h1>
<div class="container">
<div class="row column-md-7">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th class="text-right">Length (ft.) </th>
<th class="text-right">Width (ft.) </th>
<th class="text-right">Size (sq.ft.) </th>
<th class="text-center">Delete? </th>
</tr>
</thead>
<tbody>
<% #rooms.each do |room| %>
<tr>
<td> <%= link_to room.name, room %> </td>
<td> <%= room.description %> </td>
<td class="text-right"> <%= room.length %> </td>
<td class="text-right"> <%= room.width %> </td>
<td class="text-right"> <%= room.size %> </td>
<td class="text-center"> <%= link_to "delete", room, method: :delete,
data: { confirm: "You sure?" } %> </td>
</tr>
<% end %>
</tbody>
</table>
<div class="alert alert-info">
The model contains <%= pluralize(Room.count, "room") %> in total.
The max length is <%= Room.maximum('length') %>.
The max width is <%= Room.maximum('width') %>.
</div>
</div>
</div>
I tried showing 'size' by adding
The max size is <%= Room.max_room %>
but that returned an error.
new.html.erb
<% provide(:title, "New Room") %>
<h1>The Rooms page </h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#room) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :description %>
<%= f.text_area :description %>
<%= f.label :length, "Length (ft.)" %>
<%= f.number_field :length %>
<%= f.label :width, "Width (ft.)" %>
<%= f.number_field :width %>
<%= f.submit "Create my room", class: "btn btn-primary" %>
<% end %>
</div>
</div>
show.html.erb
<% provide(:title, #room.name) %>
<h1>The "<%= #room.name %>" page </h1>
<h2>This page contains the show action associated with the
Rooms page </h2>
<br>
<br>
<div class="container">
<div class="col-md-6 col-md-offset-3">
<table class="table table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th class="text-right">Length (ft.) </th>
<th class="text-right">Width (ft.) </th>
<th class="text-right">Size (sq.ft.) </th>
</tr>
</thead>
<tbody>
<tr>
<td> <%= #room.name %> </td>
<td> <%= #room.description %> </td>
<td class="text-right"> <%= #room.length %> </td>
<td class="text-right"> <%= #room.width %> </td>
<td class="text-right"> <%= #room.size %> </td>
</tr>
</tbody>
</table>
</div>
</div>
<hr>
<%= link_to "Create a new room", new_room_path, class: "btn btn btn-primary" %>
routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get 'home' => 'static_pages#home'
get 'calculations' => 'static_pages#calculations'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'new_room' => 'rooms#new'
get 'rooms' => 'rooms#index'
resources :rooms
end
I plan to use apps that will be heavy with numerical calculations so I want to get these fundamentals right. I don't want the app's database to blow up if I'm saving too many calculations down when they should be done (perhaps) in a virtual environment.
So, to recap ....
Should a app calculation be saved to the database as an attribute to a new record?
What might be the correct calculation/method for 'size'?
If I want to perform a calculation on a calculated value, does that value first have to be saved as an attribute?

Your implementation of max_room is wrong, since the size value is just a number, the max method is not defined on a number, but instead it should be called on an Enumerable of values.
So Room should be implemented this way:
class Room < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 30 },
uniqueness: { case_sensitive: false }
validates :length, :width, presence: true,
numericality: { only_integer: true,
less_than_or_equal_to: 1000,
greater_than_or_equal_to: 1 }
def size
size = length * width
end
class << self
# This is a class method, since it depends on all the rooms
# not on a specific room
def max_size
# This will delegate the calculation to the database
select('MAX(length * width) AS max')[0]['max'];
end
# But, this will instantiate the records on memory before it makes the calculation
# def max_room
# all.max{ |room| room.length * room.width }
# end
# This is a class method as well
def max_room
order('size DESC').first
end
end
end
Should a app calculation be saved to the database as an attribute to a new record?
If the attributes on which the calculated value depends will change frequently, in this case you should not save the calculated value, but rather calculate it every time you need it. But as I can see the length and the width of a room will not change, so the calculated value will need to be calculated once, and saved to be used when needed (e.g to calculate the max_size), so in this case you need to create an attribute size and calulate it when you create the record using a hook.
before_save :calculate_size
private
def calculate_size
size = length * width
end

Related

Multiple Partial Updates in Rails using Turbo Stream

I have two models, one for customer and one for complaints, one customer also has many complaints. If a complaint is added to the customer, how can you use Turbo Stream to update both the index of complaints and the show view of the customer using two different partials while ensuring only the specific customer's complaints are listed in the customer show view?
Here is some of my code:
Customer model:
class Customer < ApplicationRecord
validates_presence_of :names
belongs_to :company
has_many :complaints
broadcasts_to ->(customer) { [customer.company, "customers"] }, inserts_by: :prepend
end
Complaint model:
class Complaint < ApplicationRecord
belongs_to :company
belongs_to :customer
broadcasts_to ->(complaint) { [transgression.company, "complaints"] }, inserts_by: :prepend
end
Complaints index.html.erb (streaming fine):
<%= turbo_stream_from #company, "complaints" %>
<h1>Complaints</h1>
<%= link_to new_complaint_path, class: "btn", data: { turbo_frame: "remote_modal" } do %>
New
<% end %>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Customer</th>
<th scope="col">Date</th>
<th scope="col">Note</th>
<th colspan="2"></th>
</tr>
</thead>
<tbody id="complaints">
<%= render #complaints %>
</tbody>
</table>
Customer show.html.erb view (not streaming...):
<%= turbo_stream_from #customer %>
<%= turbo_stream_from #customer, "complaints" %>
<%= turbo_frame_tag "customer" do %>
<%= #customer.names %>
<% end %>
<%= link_to edit_employee_path(#customer), :class => "btn", data: { turbo_frame: "remote_modal" } do %>
Edit
<% end %>
<h4>Complaints</h4>
<%= link_to new_complaint_path, class: "btn", data: { turbo_frame: "remote_modal" } do %>
Add
<% end %>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Note</th>
<th colspan="2"></th>
</tr>
</thead>
<tbody id="complaints">
<%= render #complaints %>
</tbody>
</table>
</div>
On customer show page you're subscribing to #customer, "complaints" but you're not broadcasting anything to it, so there are no updates.
# app/models/complaint.rb
class Complaint < ApplicationRecord
belongs_to :company
belongs_to :customer
# NOTE: this will send turbo streams to `[company, "complaints"]`
# you have to be subscribed to that channel to receive updates.
# Defaults:
# partial: "complaints/complaint"
# target: "complaints"
broadcasts_to ->(complaint) { [complaint.company, "complaints"] }, inserts_by: :prepend
# NOTE: to receive updates on the customer show page you have send
# updates to [customer, "complaints"]
broadcasts_to ->(complaint) { [complaint.customer, "complaints"] },
inserts_by: :prepend,
partial: "customers/complaint" # <= if you want to use a different partial
end
# app/views/customers/show.html.erb
# make sure to subscribe to a channel that you're broadcasting to
<%= turbo_stream_from #customer, "complaints" %>
# you have to have an `id` target for the incoming `turbo_stream`
<div id="complaints">
# render a different partial
<%= render partial: "customers/complaint", collection: #complaints %>
</div>
I'm not sure what issues you have in the controllers, you don't need to do anything for broadcasts to work:
# app/controllers/customers_controller.rb
# GET /customers/1
def show
# NOTE: only show specific customer complaints on initial request
# broadcasts are already limited to `#customer`
#complaints = #customer.complaints
end

how can i get project_id by remarks in ruby on rails

I have manager remark model that takes input as a remark and decision value and saves it with the project site ID. I have a project site model that takes input as name, date, and file and stores it. Many remarks have a many to one relation with project site ID, and the project site belongs to the manager remark. I want to access the decision attribute boolean value in project site index form, but I am unable to access that boolean value in the index page of the project site. Here is my code of project site and manager remarks model, view and controller-
project site index.html.erb
<table>
<thead>
<tr>
<th>Name</th>
<th>Date</th>
<th>Attendance</th>
<th>Status</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #project_sites.each do |project_site| %>
<tr>
<td><%= project_site.name.titleize %></td>
<td><%= project_site.date %></td>
<td><%= link_to ' View attendance', project_site.file, :class => "fi-page-export-csv" %></td>
<td><%= "here i want to access manager remark decision value" %></td>
<td><%= link_to 'Remark ', project_site %><span>(<%= project_site.manager_remarks.size %>)</span></td>
<td><%= link_to 'Edit', edit_project_site_path(project_site) %></td>
<td><%= link_to 'Destroy', project_site, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
project site controller
def index
#project_sites = ProjectSite.all.order("created_at DESC")
#manager_remark = ManagerRemark.joins(:project_site).where(:project_sites => { :user_id => #user.id })
end
# GET /project_sites/1
# GET /project_sites/1.json
def show
#manager_remark = ManagerRemark.new
#manager_remark.project_site_id = #project_site.id
end
# GET /project_sites/new
def new
#project_site = ProjectSite.new
end
def project_site_params
params.require(:project_site).permit(:name, :date, :file)
end
manager_remark controller
class ManagerRemarksController < ApplicationController
def create
#manager_remark = ManagerRemark.new(remark_params)
#manager_remark.project_site_id = params[:project_site_id]
#manager_remark.save
redirect_to project_site_path(#manager_remark.project_site)
end
def remark_params
params.require(:manager_remark).permit(:remark, :decision)
end
end
manager_remark view form
<%= form_for [ #project_site, #manager_remark ] do |f| %>
<div class="row">
<div class="medium-6 columns">
<%= f.radio_button :decision, true %>
<%= f.label :approve %>
<%= f.radio_button :decision, false %>
<%= f.label :reject %>
</div>
<br>
<br>
<div class="medium-6 cloumns">
<%= f.label :remark %><br/>
<%= f.text_area :remark %>
</div>
</div>
<div>
<%= f.submit 'Submit', :class => 'button primary' %>
</div>
<% end %>
routes.rb
Rails.application.routes.draw do
root to: 'home#index'
devise_for :users
resources :project_sites do
resources :manager_remarks
end
get '/project_manager_level_two' => 'project_manager_level_two#index'
get '/project_managers' => 'project_managers#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
If I understand correctly, you have a ProjectSite that contains a ManagerRemark with a decision, right? If that's the case, the simple answer is:
<%= project_site.ManagerRemark.decision %>
If you are saying that each ProjectSite has many ManagerRemarks, you'll want to place the above inside a loop, like so:
<% project_site.manager_remarks.each do |manager_remark| %>
<%= manager_remark.decision %><br/>
<% end %>
This assumes that your models are correctly configured to recognize these relationships. The above may also be optimized by adding an include clause to your fetch inside the controller and there's no need to fetch the ManagerRemark objects separately. Therefore, you'd probably want something like:
def index
#project_sites = ProjectSite.all.includes( :manager_remark ).order("created_at DESC")
end

Sorting by values in a loop

I'm trying to store FIFA Games, and set a scoreboard with a ranking system.
I shouldn't use logic in the view, but if I calculate them in the controller, it renders an error that the method user is not specified. When I put it in the loop, however, it recognizes it because the user is the looped item.
The app can already save games and calculate the winner. The app adds winner_id and loser_id to each game. Later in the scoreboard, I count how many current user_id's from the loop match all games' winner_id's and loser_id's. This keeps the database clean. I don't want to keep the wins and losses in the db because when a game is deleted, it shouldn't count as a win or loss anymore.
Controller:
class ScoreboardController < ApplicationController
def index
#users = User.all
end
end
VIEW:
<div class="panel panel-default" style="margin-left: 10px; margin-right:10px">
<!-- Default panel contents -->
<div class="panel-heading">Scoreboard</div>
<!-- Table -->
<table class="table">
<thead>
<th>#</th>
<th>Username</th>
<th>Ratio</th>
<th>Wins</th>
<th>Losses</th>
</thead>
<% #users.each do |user|%>
<tbody>
<td>
1
</td>
<td>
<%= user.username %>
</td>
<% if (Game.where(:winner_id => user.id).count) == 0 %>
<td>Unvalid</td>
<% elsif (Game.where(:loser_id => user.id).count) == 0 %>
<td>Unvalid</td>
<% else %>
<% #ratio = (number_with_precision((((Game.where(:winner_id => user.id).count).to_f) / (Game.where(:loser_id => user.id).count).to_f), precision: 2)) %>
<td><%= #ratio %></td>
<% end %>
<td>
<%= Game.where(:winner_id => user.id).count %>
</td>
<td>
<%= Game.where(:loser_id => user.id).count %>
</td>
<% end %>
</tbody>
</table>
</div>
I'd like to put this list in the right order. The list should be ordered by ratio. => the #ratio from the view. Can I do this directly?
In the first td, the current position is shown. It shows 1 for every user. How can I make this 1, 2, 3, ...?
You should add those methods in your User model.
class User < ActiveRecord::Base
has_many :wins, class_name: 'Game', foreign_key: 'winner_id'
has_many :losses, class_name: 'Game', foreign_key: 'loser_id'
def ratio
wins.count / losses.count.to_f * 100
end
end
then in the controller :
def index
#users = User.all.sort_by(&:ratio)
end
and in the view, use the user instance methods directly :
<%= user.wins.count %>
You should be doing it the way #ThomasHaratyk has suggested above.
Additional question: in the first td the current position is shown, for now it shows a 1 for every user, how can I make this 1, 2 , 3 , ... ?
<% #users.each_with_index do |user, index|%>
<tbody>
<td>
<%= index + 1 %>
</td>
<% end %>

undefined method nil:NilClass ruby

What I need to do is create a page where the user will type in a last name and the system will return information related to it. I keep receiving the error "undefined method `each' for nil:NilClass." I am stuck and can not debug it any help or guidance would be greatly appreciated.
CODE FOR THE INPUT PAGE
<%= form_tag(showcustcourses_custcoursesout_path, :controller => "showcustcourses", :action => "custcoursesout", :method => "post") do %>
<div class="field">
<%= label_tag :Customer_Name %><br />
<%= text_field_tag :customer_name_in %>
</div>
<div class="actions">
<%= submit_tag "Submit Customer Name" %>
</div>
<% end %>
CODE FOR THE CONTROLLER
class BookinController < ApplicationController
def bookin
end
def bookout
#customer_name = params[:customer_name_in]
#r = Customer.find_by_last(#customer_name)
#rate_list = #r ? #r.rates : nil
end
end
CODE FOR THE OUTPUT PAGE (<% #customer_list.each do |m| %> is throwing the error)
<h1>Bookin#bookout</h1>
<p>Find me in app/views/bookin/bookout.html.erb</p>
<center><table width = 65% border = 1>
<tr> <th> Customer Name</th><th> Room Number </th> <th> Cost </th></tr>
<% #customer_list.each do |m| %>
<tr> <td> <%= m.name %> </td> <td> <%= m.roomnumber %> </td> <td> <%= m.cost %> </td> </tr>
<% end %>
</table> </center><br /> <br />
You are getting the error undefined method 'each' for nil:NilClass because you forgot to set the value of instance variable #customer_list. So, #customer_list is nil.
You need to set #customer_list variable in the action corresponding to your view which in your case is bookout action as you are rendering bookout.html.erb.
Simply, do this in BookinController#bookout:
def bookout
## ...
#customer_list = Customer.all ## Add this
end
UPDATE
As per the chat discussion, OP needed to show last(from customers table), roomlevel(from apples table), cost(from rates table)
Suggested to modify
bookout method as below:
def bookout
#customer_list = Customer.all
#customer_name = params[:customer_name_in]
end
and bookout.html.erb as below:
<% #customer_list.each do |customer| %>
<% customer.bookings.each do |booking| %>
<tr>
<td> <%= customer.last %> </td>
<td> <%= booking.apple.room_level %> </td>
<td> <%= booking.apple.cost %> </td>
</tr>
<% end %>
<% end %>
Also, OP's schema was not correct to achieve this result. Added apple_id to bookings table and removed rate_id from it.
NOTE: As you don't want bookings to be associated with rates table,rate_idwas removed from bookings table. You would have to add cost field in apples table to display the cost in the view.
Add this in your controller. It will bring details of all customers.
def bookin
#customer_list = Customer.all
end
def bookout
#customer_list = Customer.all
#customer_name = params[:customer_name_in]
#r = Customer.find_by_last(#customer_name)
#rate_list = #r ? #r.rates : nil
end
If still it returns nil error, then it means you do not have any customer records in database.
<h1>Bookin#bookout</h1>
<p>Find me in app/views/bookin/bookout.html.erb</p>
<center><table width = 65% border = 1>
<tr> <th> Customer Name</th><th> Room Number </th> <th> Cost </th></tr>
#check if object is not nil
<% if !#customer_list.nil? %>
<% #customer_list.each do |m| %>
<tr>
<td> <%= m.name %> </td>
<td> <%= m.roomnumber %> </td>
<td> <%= m.cost %>
</td>
</tr>
<% end %>
<% else %>
<p> no customers available </p>
<% end %>
</table>
</center>
#customer_list is not defined
defined it in controller like this
#customer_list = Customer.all

Conditionally calling 'collection_select' from a model/helper with AJAX

I'm looping through each instance of a built sub-tournament - and the problem that I'm having has to do with conditionally creating a collection_select box with data fetched via ajax. Here's the view - the line I want to insert code in is marked:
View
<% #tournament.sub_tournaments.each_with_index do |sub, i| %>
<%= f.fields_for :sub_tournaments, sub, :validate => false do |sub_form| %>
<div class="tab-content standings-row-<%= i %>" style="display:none">
<table>
<thead>
<tr>
<th> <h4>Standing</h4> </th>
<th class="standings-field-<%= i %>"></th>
<th></th>
</tr>
</thead>
<tbody>
<%= sub_form.fields_for :standings, :validate => false do |standings| %>
<tr>
<td>
<%= f.hidden_field :_destroy %><%= f.text_field :standing, :class => "standing", readonly: true, :type => "" %>
</td>
<td class="standings-ajax-<%= i %>">**INSERT HERE**</td>
<td><span class="remove">Remove</span></td>
</tr>
<% end %>
</tbody>
</table>
<div class="add-item">
<%= link_to_add_standings_fields(sub_form, :standings) %>
</div>
</div>
<% end %>
<% end %>
I thought about doing the conditional check (it depends upon whether the game selected is a team game or a single-player game) in the controller, but it seems to make more sense as a method (or a helper?). At the moment I have it in Standing.rb (below) - but I'm getting a no method "collection_select" error - so probably form helpers aren't available in models, which seems reasonable. So how could I do this?
Standing.rb
def team_or_player(game)
if Game::TEAM_GAMES.include?(game.name)
self.collection_select(:team_division_id, TeamDivision.where("game_id = ?", game.id),
:id, :name, {include_blank: true})
else
self.collection_select(:player_id, Player.where("game_id = ?", game.id),
:id, :handle, {include_blank: true})
end
end
And how can I pass the f to my AJAX call?
AJAX
$(".standings-ajax-<%= #tab_number %>").html("<%= ** ?? **.team_or_player(#standing, #game) %>");
You can call helper in model:
ActionController::Base.helpers.collection_select( ... )
So from what I can see you should change team_or_player() to class method and call it with:
Standings.team_or_player(#standing, #game)
or as instance
#standing.team_or_player(#standing, #game)
But that you should use self instead of passing #standing.
Me preference would be to put that logic directly in view or to helper.

Resources