Variables that store DB queries in Ruby on Rails - ruby-on-rails

I have a little Rails beginners question:
inside my Rails helper, I created method that I'm using to show a price in my view:
def price
pricea = Hotel.order(wdpricenm: :asc).first
priceb = Hotel.order(wepricenm: :asc).first
if (pricea.wdpricenm + priceb.wepricenm) < (priceb.wdpricenm + priceb.wepricenm)
return (pricea.wdpricenm + priceb.wepricenm)
else
return (priceb.wdpricenm + priceb.wepricenm)
end
<td><%= price %></td>
Its works without problems but I'd like to put the pricea / priceb variables (that store the queries) somewhere else in the rails application since I want to use them for other methods as well.
My Question is therefore: What would you suggest where to put those price variables in Rails and especially what variable types would you use?
Thanks for all replies in advance,
Cheers Rob

Use scope: http://guides.rubyonrails.org/active_record_querying.html#scopes
Class Hotel < ActiveRecord:Base
scope :pricea, -> { order(wdpricenm: :asc).first }
scope :priceb, -> { order(wepricenm: :asc).first }
end

Class Hotel < ActiveRecord:Base
def self.pricea
self.order(wdpricenm: :asc).first
end
def self.priceb
self.order(wepricenm: :asc).first
end
end
Now you are able to use:
pricea = Hotel.pricea
priceb = Hotel.priceb
If you need those prices available in different views you can do in the controllers:
Before_action :retrieve_prices, only: [:action1, :action2]
def retrieve_prices
#pricea = Hotel.pricea
#priceb = Hotel.priceb
end
This way this variables will be available only for the selected actions, and the methods can be reused anywhere.

EDIT: per comment
In the model:
Class Hotel < ActiveRecord::Base
scope :weekday_min, -> { where(minimum(:wdpricenm)).first }
scope :weekend_min, -> { where(minimum(:wepricenm)).first }
def self.best_deal(weekdays, weekend_days)
weekday_min_tot = (weekdays * weekday_min.wdpricenm) + (weekend_days * weekday_min.wepricenm)
weekend_min_tot = (weekend_days * weekend_min.wdpricenm) + (weekend_days * weekend_min.wepricenm)
[weekday_min_tot, weekend_min_tot].min
end
end
In the controller:
#best_deal = Hotel.best_deal
Display total price in the view(no hint as to which hotel though):
<%= #best_deal %>
Note the lack of helpers.
ALSO NOTE: The best deal might not be either of these choices. What you need is to calculate the total for each hotel and then take the minimum of that.
You have a bit of a journey ahead of you picking up Rails. It's a big bite. I recommend the Hartl tutorial to start with. And don't forget to learn Ruby.

Related

Create a variable in controller to order by

I have a form where a 3 of the user inputs have numbers (week1,2 and 3). I'd like to create a variable in my controller that would add the fields together.
i.e score = student.week1 + student.week2 + student.week3
I would then like to order the students on the students.index.html.erb page by the highest score. Part of the issue is my index page does not have a student.id until i go to the show or edit page etc
My student model:
class Student < ApplicationRecord
belongs_to :user
def to_param
"#{id}-#{fname.parameterize}-#{lname.parameterize}"
end
end
My Student controller
class StudentsController < ApplicationController
def index
#pagy, #students = pagy(Student.all, page: params[:page], items: 16)
#students.order([:week1_score] + [:week2_score] + [:week3_score])
end
def new
#student = Student.new
end
end
I have tried creating a
def score
week1_score = #student.week1_score
week2_score = #student.week2_score
week3_score = #student.week3_score
week1_score + week2_score + week3_score
end
But i'm guessing the user_id is whats holding me back. Aside from the ordering, i'd still like to know the score of each student. Thanks
You're approaching the problem completely wrong from the get go.
If the students table has the columns week1_score, week2_score, week3_score you should add them together in situ so that you can order the records in the database.
class StudentsController < ApplicationRecord
...
def index
#students = Student.select(
'students.*',
'(students.week1_score + students.week2_score + students.week3_score) AS total_score'
).order(:total_score)
end
...
end
Your attempt:
.order([:week1_score] + [:week2_score] + [:week3_score])
is actually a really bad equivalent to calling:
.order(:week1_score, :week2_score, :week3_score)
This will order by the three columns - not by the total.

Rails controller variable mess

I have a controller that I feel has too many instance variables.
The controller is pulling data from various places and it feels really sloppy.
I have watched some Sandi Metz talks, read books, and other research, and I want to have good practice but I just don't know what to do here.
This method is pulling all the data and sending it to my view and I am able to get it to work, I just know this isn't a good way to go about it and I am hoping someone can point me to some code samples, documentation, videos, or help me understand how to implement a better style.
I have searched on SO and Google but I mostly find people saying to send a hash or JSON to the view, and I want to know if that is ideal before I start on that.
The Client, Project, Person, Role controllers and models have really similar code and I am working on refactoring it to be more DRY.
For example the Client, Project, Person, and Role financial controllers have almost the exact same controller index code as this. :(
I would be happy to add more code if that would help!
This is the project_financials_controller#index
It's pretty much taking in the data from the view and pulling a bunch of data from the database and sending it to a view. I'm currently using only the index method because it was only supposed to be a 'view' but now we can add filters such as time, different clients, etc so I think I need to break it out somehow.
I do have a financial_reports_nav model that this is calling that I could maybe use more, Or even make a financial_reports_controller that pulls the data from the appropriate model and I wont even need the 4 different controllers...
I am totally open to any input/criticism!
def index
# CPPR = Client, Project, Person, Role
#financial_type = 'project'
#financial_params = params
# This pulls the timeframe from the view and figures out the dates requested. (eg. "Last Week")
#timeframe = Financial.time_frame(#financial_params[:timeframe], current_company.timezone, params[:start_date], params[:end_date])
# This grabs all the data required to recall this financial report view at a later time
#financial_nav = FinancialReportNav.set_financial_type(#current_user.id,#financial_type, #start_date, #end_date)
# Grab all active and inactive people for client
#people = Person.active.all
#deleted_people = Person.inactive.all
# This sends over all the info needed to generate the financial reports
#project_financial_populate = Financial.new(#financial_params, #financial_type).populate_project_financials(current_company.default_hourly_cost, current_company.billing_rate, #timeframe[:start_date],#timeframe[:end_date])
# This just pulls all the data from the database that the #project_financial_populate just populated (Can't we just use that??)
#financial_rows = ProjectFinancial.all.map { |p| [ p.project_id, p.billable_hours, p.revenue,p.real_rate, p.hourly_expense, p.labor_expense_total, p.salary_expense, p.gross_profit, p.profit_margin, p.missing_hourly_expense, p.missing_billable_rate ] }
# Using the same view for CPPR's
# Clients has an items count, so we just stuff everything into the first array slot
#items = [1]
# If these are not null then they show an option to change the financial filter type.
#filter_by_client = Client.find_by('id = ?', #financial_params[:filter_by_client])
#filter_by_project = Project.find_by('id = ?', #financial_params[:filter_by_project])
#filter_by_person = Person.find_by('id = ?', #financial_params[:filter_by_person])
#filter_by_role = PersonRole.find_by('id = ?', #financial_params[:filter_by_role])
# This pulls a list of CPPR's that have tracked time in the requested timeframe
#project_list = Financial.project_list(#timeframe[:start_date], #timeframe[:end_date])
#client_list = Financial.client_list(#timeframe[:start_date], #timeframe[:end_date])
#people_list = Financial.people_list(#timeframe[:start_date], #timeframe[:end_date])
end
I always tend to refactor code to be DRY whenever I noticed I have at least 3 instances of duplicate code, but I needed to future-proof the new code to be flexible enough for possible future changes; all of this considered however time permits.
Given your already current code and having told my preferences, this is what I would do:
Model Inheritance
Controller Inheritance
Shared template
Routes
config/routes.rb
resources :client_financial
resources :project_financial
resources :person_financial
resources :role_financial
Models
app/models/financial_record.rb
class FinancialRecord < ActiveRecord::Base # or ApplicationRecord if > Rails 5
self.abstract_class = true
# your shared "financials" model logic here
end
app/models/client_financial.rb
class ClientFinancial < FinancialRecord
# override "financials" methods here if necessary
# or, add new model specific methods / implementation
end
app/models/project_financial.rb
class ProjectFinancial < FinancialRecord
# override "financials" methods here if necessary
# or, add new model specific methods / implementation
end
app/models/person_financial.rb
class PersonFinancial < FinancialRecord
# override "financials" methods here if necessary
# or, add new model specific methods / implementation
end
app/models/role_financial.rb
class RoleFinancial < FinancialRecord
# override "financials" methods here if necessary
# or, add new model specific methods / implementation
end
Controllers
app/controllers/financial_controller.rb
class FinancialController < ApplicationController
before_action :set_instance_variables, only: :index
protected
def set_instance_variables
# strips the last "Controller" substring and change to underscore: i.e. ProjectFinancialsController becomes project_financials
#financial_type = controller_name[0..(-'Controller'.length - 1)].underscore
# get the corresponding Model class
model = #financial_type.camelcase.constantize
# get the correspond Financial Model class
financial_model = "#{#financial_type.camelcase}Financial".constantize
#financial_params = params
#timeframe = Financial.time_frame(#financial_params[:timeframe], current_company.timezone, params[:start_date], params[:end_date])
# I dont know where you set #start_date and #end_date
#financial_nav = FinancialReportNav.set_financial_type(#current_user.id,#financial_type, #start_date, #end_date)
# renamed (or you can set this instance variable name dynamically)
#records = model.active.all
# renamed (or you can set this instance variable name dynamically)
#deleted_records = model.inactive.all
#financial_populate = Financial.new(#financial_params, #financial_type).populate_project_financials(current_company.default_hourly_cost, current_company.billing_rate, #timeframe[:start_date],#timeframe[:end_date])
#financial_rows = financial_model.all.map { |p| [ p.project_id, p.billable_hours, p.revenue,p.real_rate, p.hourly_expense, p.labor_expense_total, p.salary_expense, p.gross_profit, p.profit_margin, p.missing_hourly_expense, p.missing_billable_rate ] }
#items = [1]
#filter_by_client = Client.find_by('id = ?', #financial_params[:filter_by_client])
#filter_by_project = Project.find_by('id = ?', #financial_params[:filter_by_project])
#filter_by_person = Person.find_by('id = ?', #financial_params[:filter_by_person])
#filter_by_role = PersonRole.find_by('id = ?', #financial_params[:filter_by_role])
#project_list = Financial.project_list(#timeframe[:start_date], #timeframe[:end_date])
#client_list = Financial.client_list(#timeframe[:start_date], #timeframe[:end_date])
#people_list = Financial.people_list(#timeframe[:start_date], #timeframe[:end_date])
end
end
app/controllers/client_financials_controller.rb
class ClientFinancialsController < FinancialController
def index
render template: 'financials/index'
end
end
app/controllers/project_financials_controller.rb
class ProjectFinancialsController < FinancialController
def index
render template: 'financials/index'
end
end
app/controllers/person_financials_controller.rb
class ProjectFinancialsController < FinancialController
def index
render template: 'financials/index'
end
end
app/controllers/role_financials_controller.rb
class ProjectFinancialsController < FinancialController
def index
render template: 'financials/index'
end
end
Views
app/views/financials/index.html.erb
<!-- YOUR SHARED "FINANCIALS" INDEX HTML HERE -->
P.S. This is just a simple refactor. Without knowing the fuller scope of the project, and future plans, I'll just do this one. Having said this, I would consider using "polymorpic" associations, and then just have one routes endpoint (i.e. resources :financials) and then just pass in a params filter like: params[:financial_type] which directly already map the financial_type polymorphic column name.

Method to return a collection - Ruby

Say, I have a method called posted_listings, which is supposed to run an ActiveRecord query and return a collection of User.listings where posted: true, with posted? being a Listing class method. So far I have been doing:
class Bar < ActiveRecord::Base
def posted_listings
posted_listings = []
listings.each { |listing| posted_listings << listing if listing.posted? }
posted_listing
end
end
but each time this query runs I start feeling really bad about my skills (or lack of thereof). What is the most efficient way to return a collection of posted listings?
Edit:
posted? is not an attribute, its a class method:
class Listing < ActiveRecord::Base
def posted?
true if quantity >= 1 && has_sellers?
end
end
def has_sellers?
sellers.count >=1 #association to Seller
end
I would recommend adding a scope to your Listing model like this:
scope :posted, -> { where(posted: true) }
Then you can get all posted listings like this:
#user.listings.posted
You can learn more about scopes here if you are interested.
UPDATE
Try this scope instead:
def self.posted
joins(:sellers)
.where('posted = ? AND quantity > ?', true, 0)
.group('listings.id')
.having('COUNT(sellers.id) > ?', 0)
end
Your question is not so clear for me.
You may try:
User.listings.where(posted: true)
to get all users' posted Listings.
Or, saying #useris an User instance:
#user.listings.where(posted: true)
to get posted Listings from an specific user.

Count Records within 30 days of today

I Have a model an Opportunity model that has an attribute called date_of_opportunity. I am trying to write a method that counts how many opportunities are within 30 days of today. However when I try to call my method in the console, I get the error 'undefined local variable or method'
Here is my model:
class Opportunity < ActiveRecord::Base
def calculate_num_days
num_days = 0
#opportunity = Opportunity.all
#opportunity.each do |opportunity|
if (opportunity.date_of_opportunity - Date.today < 30)
num_days = num_days + 1
return num_days
end
end
end
end
Can someone help me figure out whats wrong? Thanks!!
If you will get counts how many opportunities are within 30 days of today, you can try this :
class Opportunity < ActiveRecord::Base
def self.calculate_num_days(from = (Date.today-1.month).beginning_of_day,to = Date.today.end_of_day)
where(date_of_opportunity: from..to).count
end
end
And on your console you can type like this
Opportunity.calculate_num_days
Output looks like :
irb(main):001:0> Opportunity.calculate_num_days
←[0m←[1m←[35m (51.0ms)←[0m SELECT COUNT(*) FROM "opportunities" WHERE ("opportunities"."date_of_opportunity" BETWEEN '2014-05-04 00:00:00.000000' AND '2014-06-04 23:59:59.999999')
=> 2
You seem to want a class method, but are defining an instance method. Do this:
def self.calculate_num_days
...
end
Maybe, #opportunity = Opportunity.all should be #opportunities = Opportunity.all
Unless I am missing what you are trying to do I would let ActiveRecord do the heavy lifting. Opportunity.where("date_of_opportunity - :today < 30", today: Date.today).size
Disclaimer, this is completely untested

Is it possible to define two methods in a Rails model that require different initialization?

Hi I'm attempting to create a model in Rails that can perform two calculations. This is my code:
class Calculator
def initialize(nair, cppy, interest_rate, payment, periods)
#nair = nair.to_f / 100
#cppy = cppy.to_f
#interest_rate = interest_rate
#payment = payment
#periods = periods
end
def effective
Refinance::Annuities.effective_interest_rate(#nair, #cppy)
end
def principal
Refinance::Annuities.principal(#interest_rate, #payment, #periods)
end
end
I have two forms that reside in different views that take input from the user including 'nair' and 'cppy' on one and 'interest_rate', 'payment' and 'periods' on the other.
The problem I've run into is that to use this model all five arguments need to be available.
Do I need to have separate models for each calculation?
I'm a complete beginning sorry if there is a really obvious answer.
Thanks!
There's probably a dozen different ways you could solve this, but one possible approach would be to use default arguments in your initialize method.
class Calculator
def initialize(nair=0, cppy=0, interest_rate=0, payment=0, periods=0)
#nair = nair.to_f / 100
#cppy = cppy.to_f
#interest_rate = interest_rate
#payment = payment
#periods = periods
end
def effective
Refinance::Annuities.effective_interest_rate(#nair, #cppy)
end
def principal
Refinance::Annuities.principal(#interest_rate, #payment, #periods)
end
end
Another possible solution is to make them class methods and not deal with instances or state:
class Calculator
def self.effective(nair, cppy)
nair = nair.to_f / 100
cppy = cppy.to_f
Refinance::Annuities.effective_interest_rate(nair, cppy)
end
def self.principal(interest_rate, payment, periods)
Refinance::Annuities.principal(interest_rate, payment, periods)
end
end
Calculator.effective(x, y)
Calculator.principal(x, y, z)

Resources