Create a variable in controller to order by - ruby-on-rails

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.

Related

How to connect with different tables depending on parameter

I have model Board and BoardController where i can find all boards of my project.
All records have filled "board_layout" column with enum.
Now i have show method in BoardController and i want to load there different data from different table depending on board_layout column value.
I could do it like this:
def show
#board = Board.find(params[:id])
if #board.board_layout == 1
#tasks = Car.where(board_id: #board.id)
elsif #board.board_layout == 2
#tasks = Truck.where(board_id: #board.id)
end
end
But it's not elegant + it is not DRY (i need to use some sort of if statement anytime when i want to do something with those 2 tables).
So i have tried to create Concern and create case statement inside, now it looks like:
def show
#board = Board.find(params[:id])
#tasks = get_class_name(#board.board_layout).where(board_id: #board.id)
end
# inside my concern
def get_class_name(scope)
case scope
when 1
Car
when 2
Truck
end
end
My Question:
Is there better way to do it? Is my solution safe and clear?
What is the best solution to resolve problem like this?
I would appreciate any help.
maybe you can abstract that out into a class, so you can define multiple layout and their correspondent classes, like:
class LayoutClassGetter
CLASS_BY_LAYOUT = { '1' => Car, '2' => Truck }.freeze
def initialize(layout_number)
#layout_number = layout_number
end
def layout_class
CLASS_BY_LAYOUT[#layout_number]
end
def self.layout_class(layout_number)
new(layout_number).layout
end
end
And then use it:
def show
#board = Board.find(params[:id])
#tasks = layout_class(#board.board_layout).where(board_id: #board.id)
end
def layout_class(scope)
LayoutClassGetter.layout_class(scope)
end

Ruby add variables to an existing object?

How can I add variables to an existing obejct?
I have a list of chat rooms and I want to add a new variable for each chat to use at my view:
Example I want to add total users of chat
def index
chats_all = ChatRoom.all
#chats = Array.new
chats_all.each |chat|
chat.total_users = 10
#chats << chat
end
#chats
end
total_users is not an attribute of ChatRoom class.
[EDIT - explaim better after #jvillian great awnser]
I don't want total_users as an attribute of User class.
I just want to add as a variable to use at this one single page. For json rails already let my add new attributes to objects. Just need to use as_json().map and a merge()
Example:
def index
chats = chats.as_json().map {
|chat|
chat.merge(
total_users: 10
}
response = { chats: chats }
render json: response
end
Now I got an json with chats and each chat has total_users attribute.
I want to know if I can do something like this with objects, just add a temporary variable to use at index page.
Try
class ChatRoom < ActiveRecord::Base
attr_accessor :total_users
end
You can read more in the docs.
Then, index could look like:
def index
#chats = ChatRoom.all.map do |chat|
chat.total_users = 10
chat
end
end
Alternatively, I would be tempted to do something like:
class ChatRoom < ActiveRecord::Base
TOTAL_USERS = 10
attr_accessor :total_users
def total_users
#total_users || TOTAL_USERS
end
end
And then:
def index
#chats = ChatRoom.all
end
Now, you'll get
#chats.first.total_users
=> 10
You can set total_users to something else if you like, but it will default to 10.
Here's a potential approach using OpenStruct:
def index
#chats = ChatRoom.all.map do |chat|
OpenStruct.new(
chat.
attributes.
merge!(total_users: 10)
)
end
end
Now, you can do:
#chats.each do |chat|
puts chat.total_users
end
which will return 10.
BTW and TBH, I do something like that last sort of thing (using OpenStruct or custom decorators) all the time. In my more recent apps, views never have direct access to models.
Maybe you want to send the response to the view as an array and scan to show informations?
def index
#chats = ChatRoom.all.as_json().map { |chat| chat.merge("total_users" => 10) }
end
Then access #chats, which is actually an array of hashes, view:
<% #chats.each do |chat| %>
<p><%= chat["total_users"] %></p>
<% end %>
You can check how #chats is structured by <p><%= #chats %></p>
I maybe made some syntax error.
To create temporary custom Objects without add new attributes to database Struct solve my problem.
I can create a Struct with chat room info and total users
chat_info = Struct.new(:name, :total_users, :messages)
chat_temp = []
chats = ChatRoom.where(condition)
chats.each do |chat|
chat_temp << chat_info.new("nome", 100, messages)
end

push pop recenlty viewed session data with ruby on rails

The following code it taken from a spree_recently_viewed gem
Controller
after_action :recently_viewed, only: :show
def recently_viewed
id = #product.id
rvp = (session['recently_viewed_products'] || '').split(', ')
rvp.delete(id)
rvp << id unless rvp.include?(id.to_s)
rvp_max_count = 5
rvp.delete_at(0) if rvp.size > rvp_max_count.to_i
session['recently_viewed_products'] = rvp.join(', ')
end
Helper
module ProductsHelper
def cached_recently_viewed_products_ids
(session['recently_viewed_products'] || '').split(', ')
end
def cached_recently_viewed_products
Product.find_by_array_of_ids(cached_recently_viewed_products_ids)
end
end
Model
class Product < ActiveRecord::Base
def self.find_by_array_of_ids(ids)
products = Product.where('id IN (?)', ids)
ids.map { |id| products.detect { |product| product.id == id.to_i } }.compact
end
end
looking at this line in the controller
rvp.delete_at(0) if rvp.size > rvp_max_count.to_i
it is only replacing the value at index 0. Is there a way I can add push pop style so that when a new record is added value at 0 for example moves to 1 and 1 to 2 and so on and the last one gets poped out. and if already exist moves to index 0.
Use array #unshift method to push into the beginning of an array, and #shift to pop from the beginning of the array.
PS: According the whole controllers and models code. I don't know why both too comlpex... models method ::find.. can be simplified to just Product.where('id IN (?)', ids) or to Product.where(id: ids) (thanx to #bbozo) in the newer version of Rails.

Variables that store DB queries in 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.

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

Resources