Rails: NameError in LineItemsController#create - ruby-on-rails

undefined local variable or method `line_items' for #<Cart:0x000000000ce78a18>
Extracted source (around line #3):
1 class Cart < ApplicationRecord
2 def add_product(product)
3 current_item = line_items.find_by(product_id: product.id)
4 if current_item
5 current_item.quantity += 1
6 else
app/controllers/line_items_controller.rb:27:in `create'
25 def create
26 product = Product.find(params[:product_id])
27 #line_item = #cart.add_product(product)
28
29 respond_to do |format|
30 if #line_item.save
I was running an example from Agile Web Development with Rails 6
Sam Ruby, David Bryant Copeland with Dave Thomas, on page 134 (Chapter 10. Task E: A Smarter Cart • 134), and encountered this issue.
I was wondering whether anyone has run on the same issue and how was it resolved...? Any input would be highly appreciated.

I think #rmlockerd that line_items is meant to be an instance variable, or line_items could also be LineItem class
1 class Cart < ApplicationRecord
2 def add_product(product)
3 current_item = LineItem.find_by(product_id: product.id)
4 if current_item.present?
5 current_item.quantity += 1
6 else

Related

Rails 5 find adult users

I've got pretty old App where I have to create rake task to find all users over 18 and update flags from adult: false to adult: true. I'm wondering what I should use in a rather old version of Rails (I have Rails 5 and Ruby 2.4 on board) to keep the highest performance?
What I have for now is a sidekiq worker with, I think, some syntax error:
class MinorsWorker
include Sidekiq::Worker
def perform
adults = User.where(adults: false).where('date_of_birth >= 18, ?', ((Time.zone.now - date_of_birth.to_time) / 1.year.seconds))
adults.update(adult: true)
end
end
But this code gives me an error:
NameError: undefined local variable or method `date_of_birth' for main:Object
you can do the following. This would update all the matched records in 1 update statement.
If you are concern with db IO, you can batch it.
# in user.rb
class User
scope :adult, -> { where('date_of_birth <= ?', 18.years.ago) }
end
# in your worker file
class MinorsWorker
include Sidekiq::Worker
def perform
update_all
# for update in batches, use #update_all_in_batches
end
private
def update_all
User.adult.where(adult: false).update_all(adult: true)
end
def update_all_in_batches
User.adult.where(adult: false).in_batches(each: 1000) do |users|
users.update_all(adult: true)
sleep 2
end
end
end

Refactoring rails nested methods

Im trying to refactor my code in the Team model. Teams has:many Projections, and Projections has stats for a player (goals, assists, hits etc). I want to implement one method that can tally a specific total if you pass it the parameter you are looking for.
Something like this, where I could call:
example_team.total(goals)
or
example_team.total(assists)
class Team < ApplicationRecord
# def total(value)
# total = 0
# self.projections.each do |projection|
# total += projection.value
# end
# return total
# end
The issue im having is that im essentially trying to pass a method name within a different method as a parameter. Im getting errors when im trying to do this. How can I get this refactoring to work???
This is my current unfactored code:
class Team < ApplicationRecord
def goals
total = 0
self.projections.each do |projection|
total += projection.goals
end
return total
end
def assists
total = 0
self.projections.each do |projection|
total += projection.assists
end
return total
end
def pp_points
total = 0
self.projections.each do |projection|
total += projection.pp_points
end
return total
end
def hits
total = 0
self.projections.each do |projection|
total += projection.hits
end
return total
end
def blocks
total = 0
self.projections.each do |projection|
total += projection.blocks
end
return total
end
To send a method dynamically you can use public_send.
class Team < ApplicationRecord
def total(field)
total = 0
self.projections.each do |projection|
total += projection.public_send(field)
end
return total
end
end
To simplify the example we can use sum that takes a symbol as an argument.
class Team < ApplicationRecord
def total(field)
projections.sum(field)
end
end
To make it work you should use something like this example_team.total(:goals).

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.

Update column in database from model

So I have a User model and a Post model
Post belongs to a User
Post has a field in the database called score
In the Post model I have a method called score which gives the post a score based on the fields (needs to be done this way):
def score
score = 0
if self.title.present?
score += 5
end
if self.author.present?
score += 5
end
if self.body.present?
score += 5
end
score
end
The Question:
There are loads of Users and loads of Posts. So What I'm trying to do is after the score is worked out, I want to save it to the Post score field in the database for each Post. The score should be updated if the user updates the Post.
I have looked at using after_update :score! but don't understand how to apply the logic
It looks a little like you are trying to re-invent the wheel ActiveRecord provides you.
If you have a database field score, then ActiveRecord will automatically provide an attribute_reader and attribute_writer for score and you should not override these unless you have a really really good reason for it, e.g. you need to add some other resources or some serious business logic into it.
There is a way easier way to solve it, by using the before_save hook, which will kick in before any #create or #update:
class Post
attribute_accessible :score # if you have Rails 4.x you can omit this line
before_save :update_score
private
def update_score
new_score = 0
self.score = [:title, :author, :body].each do |field|
new_score += 5 if send(field).present?
end
self.score = new_score
end
This way, ActiveRecord will handle the saving for you and your score will always up to date. Additionally Post#score will always return the real value currently saved in the database
You can do it like this
after_update :score!
def score!
score = 0
if self.title.present?
score += 5
end
if self.author.present?
score += 5
end
if self.body.present?
score += 5
end
self.update_column(:score, score)
end
This is to be done in your Post model.
You can do it using update_column method. Like:
def score
score = 0
if self.title.present?
score += 5
end
if self.author.present?
score += 5
end
if self.body.present?
score += 5
end
self.update_column(:score, score)
end
You need to override the setter method in the Post model
attr_accessible :score
def score=(value)
score = 0
if self.title.present?
score += 5
end
if self.author.present?
score += 5
end
if self.body.present?
score += 5
end
write_attribute(:score, score)
end

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