NoMethodError in controller - ruby-on-rails

Hello I'm new at Ruby and I'm trying to make a method in my Project controller like so:
def update_phase
#project = Project.find(params[:id])
diff = (Date.current.year * 12 + Date.current.month) - (#project.starting.year * 12 + #project.starting.month)
case
when diff >= 30
#project.process = 11
.
.
.
when diff >= 0
#project.process = 1
else
#project.process = 0
end
proc = #project.process.to_f
case
when proc >= 9
#project.phase = "Final"
when proc >= 5
#project.phase = "Desarrollo"
when proc >= 1
#project.phase = "Inicio"
else
#project.phase = "Error en el proceso"
end
end
starting is a timestamp in the model. In my view I have:
<% #project.update_phase %>
but I get the error: "NoMethodError in Projects#show"
how can I fix this?

Depending on what's or where does starting come from, you could use a before_save callback, this way everytime you're going to create a new record, it triggers the update_phase method and assigns the values for process and phase from the current project object:
class Project < ApplicationRecord
before_save :update_phase
...
def update_phase
diff = (Date.current.year * 12 + Date.current.month) - (self.starting.year * 12 + self.starting.month)
case
when diff >= 30
self.process = 11
...
end
proc = self.process.to_f
case
when proc >= 9
self.phase = 'Final'
...
end
end
end

Related

Group users by age range in rails

Based on 'PESEL" number I have to group user by their age. I created something like this and it is working, but... To be honest, it look bad for me.
HELPER:
def years(pesel)
years = (0..99).to_a
birth_year = []
case pesel[2..3].to_i
when 0..19
20.times do |index|
first_number = index % 2 == 0 ? (5 * index) : ((5 * index))
second_number = index % 2 == 0 ? (5 * index + 4) : ((5 * index) + 4)
first_year = Date.today.year - second_number.to_s.rjust(4,'1900').to_i
second_year = Date.today.year - first_number.to_s.rjust(4,'1900').to_i
birth_year += ["#{first_year}-#{second_year}"]
end
multiplied_birth_years = ([birth_year] * 5).inject(&:zip).flatten
hash = Hash[years.zip multiplied_birth_years]
hash.fetch(pesel[0..1].to_i)
when 20..39
20.times do |index|
first_number = index % 2 == 0 ? (5 * index) : ((5 * index))
second_number = index % 2 == 0 ? (5 * index + 4) : ((5 * index) + 4)
first_year = Date.today.year - second_number.to_s.rjust(4,'2000').to_i
second_year = Date.today.year - first_number.to_s.rjust(4,'2000').to_i
birth_year += ["#{first_year}-#{second_year}"]
end
multiplied_birth_years = ([birth_year] * 5).inject(&:zip).flatten
hash = Hash[years.zip multiplied_birth_years]
hash.fetch(pesel[0..1].to_i)
when 40..59
20.times do |index|
first_number = index % 2 == 0 ? (5 * index) : ((5 * index))
second_number = index % 2 == 0 ? (5 * index + 4) : ((5 * index) + 4)
first_year = Date.today.year - second_number.to_s.rjust(4,'2100').to_i
second_year = Date.today.year - first_number.to_s.rjust(4,'2100').to_i
birth_year += ["#{first_year}-#{second_year}"]
end
multiplied_birth_years = ([birth_year] * 5).inject(&:zip).flatten
hash = Hash[years.zip multiplied_birth_years]
hash.fetch(pesel[0..1].to_i)
end
end
CONTROLLER:
def grouped_by_age
#yearsbook = #study_participations.includes(user: :profile).group_by do |study_participation|
years(study_participation.user.profile.pesel)
end
end
A small explanation and example. I am interested in first 6 numbers that correspond sequentially: Year of birth, month, day
So if my PESEL == '980129(...)', then I was born twenty-ninth of January 1998
If someone was born in year 2000, then we add 20 to pesel-month number(for example '002129(...)' it is twenty-ninth of January 2000. If someone was born 2100, then we add 40 to pesel-month number.
I have explained what the pesel number is all about, now what I want to do with it.
I need to group users by their age range. Function from above returns has like this:
{0=>"118-122",
1=>"118-122",
2=>"118-122",
3=>"118-122",
4=>"118-122",
5=>"113-117",
6=>"113-117",
7=>"113-117",
8=>"113-117",
9=>"113-117",
10=>"108-112",
11=>"108-112",
12=>"108-112",
13=>"108-112",
14=>"108-112",
15=>"103-107",
16=>"103-107",
17=>"103-107",
18=>"103-107",
19=>"103-107",(...)}
Unfortunately this is not very efficient, because for each user (4000 max) I have to execute the functions from scratch. Is there any way to increase efficiency of this? I thought about storing this hash as const and changing it once a year, but I don't really know how to do that or if it is possible.
EDIT:
Forgot to mention: I need to compare user age with hash, so I can extract age range
EDIT2:
Based on #yoones answer I created something like this:
HELPER:
def years_cache
years = []
201.times do |index|
years += [Date.today.year - (1900 + index)]
end
birth_year = []
60.times do |index|
year = if index < 20
'1900'
elsif index < 40
'2000'
else
'2100'
end
first_number = 5 * (index % 20)
second_number = (5 * (index % 20)) + 4
first_year = Date.today.year - second_number.to_s.rjust(4, year).to_i
second_year = Date.today.year - first_number.to_s.rjust(4, year).to_i
birth_year += ["#{first_year}-#{second_year}"]
end
multiplied_birth_years = ([birth_year] * 5).inject(&:zip).flatten
#hash = (years.zip multiplied_birth_years).to_h
end
def years(cache, pesel)
day = pesel[4..5]
case pesel[2..3].to_i
when 0..19
month = pesel[2..3]
year = pesel[0..1].prepend('19')
when 20..39
month = (pesel[2..3].to_i - 20).to_s
year = pesel[0..1].prepend('20')
when 40..59
month = (pesel[2..3].to_i - 40).to_s
year = pesel[0..1].prepend('21')
end
birth_date = Time.strptime("#{day}/#{month}/#{year}", '%d/%m/%Y')
age = ((Time.zone.now - birth_date) / 1.year.seconds).floor
cache.fetch(age)
end
CONTROLLER:
def grouped_by_age
cache = years_cache()
#yearsbook = #study_participations.includes(user: :profile).group_by do |study_participation|
years(cache, study_participation.user.profile.pesel)
end
end
Instead of doing the complicated calculating of birth date from PESEL every time you want to view the page, do it once and store it in the database. Having a birth date column on the user makes a lot of sense.
Then when you want to group them, you can even do it via the database. If you still need to do it in ruby, then getting the birth year is as easy as user.birth_date.year
In order to then group users into ranges of 5 years according to age, add an age_range method to the model and group by that.
#study_participations.includes(user: :profile).group_by do |study_participation|
study_participation.user.age_range
end
Where age_range can be for example
def age_range
(Date.today.year - birth_date.year) / 5) * 5
end
Format that however you like
I guess you could at least build the cache once then use it in your loop. The following code is not pretty, it's just to illustrate what I mean:
def build_year_cache(index, rjust_str)
first_number = 5 * index
second_number = index % 2 == 0 ? (5 * index + 4) : ((5 * index) + 4)
first_year = Date.today.year - second_number.to_s.rjust(4, rjust_str).to_i
second_year = Date.today.year - first_number.to_s.rjust(4, rjust_str).to_i
"#{first_year}-#{second_year}"
end
def build_years_cache
cache = {}
years = (0..99).to_a
[
[0..19, '1900'],
[20..39, '2000'],
[40..59, '2100']
].each do |range, rjust_str|
birth_year = []
20.times do |index|
birth_year.append(build_year_cache(index, rjust_str))
end
multiplied_birth_years = ([birth_year] * 5).inject(&:zip).flatten
cache[range] = Hash[years.zip multiplied_birth_years]
end
cache
end
def years(pesel, cache)
year = pesel[0..1].to_i
month = pesel[2..3].to_i
range = cache.keys.find { |k, v| k.include?(month) }
cache[range].fetch(year)
end
def grouped_by_age
cache = build_years_cache
#yearsbook = #study_participations.includes(user: :profile).group_by do |study_participation|
years(study_participation.user.profile.pesel, cache)
end
end

Rails survey calculating results and storing results in DB

Having trouble with the controller of my survey (Waterusage). It has 30+ variables collected from a form and those inputs need to be saved to the waterusage db and used to calculate a final score, also saved in the database.
class Waterusage < ApplicationRecord
belongs_to :user
end
class WaterusagesController < ApplicationController
def new
#waterusage = Waterusage.new
end
def create
#user = User.find(params[:user_id])
_showerTotal = :average_shower * :shower_flow_rate * :household_size
_bathTotal = :bath_rate * :bath_multiplier * 35
_bathroomSinkTotal = :bathroom_sink_usage * :bathroom_sink_flow_rate * :household_size
_toiletTotal = :mellow * :low_flow_toilet * :household_size
_kitchenTotal = :kitchen_sink_usage * :kitchen_sink_flow_rate
_dishwashingTotal = :dishwasher_rate * :dishwasher_multiplier * :dishwasher_method
_laundryTotal = :laundry_rate * :laundry_method * :laundry_multiplier
_homeUsage = _showerTotal + _bathTotal + _bathroomSinkTotal + _toiletTotal + _kitchenTotal + _dishwashingTotal + _laundryTotal + :greywater
_lawnTotal = :lawn_rate * :lawn_multiplier * :lawn_size * :xeriscaping
_swimmingTotal = (:swimming_pool / 365) + (:swimming_months * 1000 / 365
_carwashTotal = :carwash_rate * :carwash_multiplier * :carwash_method
_outsideUsage = _lawnTotal + _swimmingTotal + _carwashTotal
_drivingTotal = 0.735 * :miles
_powerTotal = :statewater * :percent_statewater / 100
_indirectTotal = :shopping + :paper_recycling + :plastic_recycling + :can_recycling + :textile_recycling + :diet + (200 * :pet_cost / 30)
:household_total = _homeUsage + _outsideUsage + _drivingTotal + _powerTotal + _indirectTotal
:individual_total = :household_total / :household_size
#waterusage = #user.waterusage.create(waterusage_params)
redirect_to user_path(#user)
end
def destroy
#user = User.find(params[:user_id])
#waterusage = #user.waterusage.find(params[:id])
#waterusage.destroy
redirect_to user_path(#user)
end
private
def waterusage_params
params.require(:waterusage).permit(:household_size, :average_shower,
:shower_flow_rate, :bath_rate, :bath_multiplier, :bathroom_sink_usage,
:bathroom_sink_flow_rate, :mellow, :low_flow_toilet, :kitchen_sink_usage,
:kitchen_sink_flow_rate, :dishwasher_rate, :dishwasher_multiplier,
:dishwasher_method, :laundry_rate, :laundry_multiplier, :laundry_method,
:greywater, :lawn_rate, :lawn_multiplier, :lawn_size, :xeriscaping,
:swimming_pool, :swimming_months, :carwash_rate, :carwash_multiplier,
:carwash_method, :miles, :statewater, :percent_statewater, :shopping,
:paper_recycling, :plastic_recycling, :can_recycling, :textile_recycling,
:diet, :pet_cost, :individual_total, :household_total)
end
end
Is there a better way I can be doing this? Currently there are errors on the lines that are working to sum subtotals. (ie. :household_total = _homeUsage + _outsideUsage + _drivingTotal + _powerTotal + _indirectTotal
)
Also I'm not sure if I am properly connecting the user info to the survey schema
You don't want to do that math in the controller. Skinny controllers, fat models. Aside from that, one reason it's failing is that the syntax is incorrect. A symbol (:hello_world) can't be assigned a value nor does it contain one. Less importantly, while it's not illegal to have an underscore prefixed local variable, that is not the convention in Ruby. Neither is camelcase. You want hello_world rather than helloWorld. Anyway...
Assumption: You have a requirement that the totals must be persisted. They cannot be calculated values.
You want to move those calculations to the model. And instead of assigning a ton of variables, use methods. That way you can easily unit test them.
What's missing here: Validations in the model that ensure that all expected attribute values are present. The controller should handle an invalid Waterusage instance on create, too. This code is untested and is just for illustrative purposes.
class Waterusage < ApplicationRecord
belongs_to user
before_validation :calculate_totals
def calculate_totals
self.household_total = get_household_total
self.individual_total = get_individual_total
end
def get_household_total
home_usage + outside_usage + driving_total + power_total + indirect_total
end
def get_individual_total
household_total / household_size
end
def home_usage
shower_total + bath_total + bathroom_sink_total + toilet_total + kitchen_total + dishwashing_total + laundry_total + greywater
end
def outside_usage
lawn_total + swimming_total + carwash_total
end
def driving_total
0.735 * miles
end
def power_total
statewater * percent_statewater / 100
end
def indirect_total
shopping + paper_recycling + plastic_recycling + can_recycling + textile_recycling + diet + (200 * pet_cost / 30)
end
def shower_total
average_shower * shower_flow_rate * household_size
end
def bath_total
bath_rate * bath_multiplier * 35
end
def bathroom_sink_total
bathroom_sink_usage * bathroom_sink_flow_rate * household_size
end
def toilet_total
mellow * low_flow_toilet * household_size
end
def kitchen_total
kitchen_sink_usage * kitchen_sink_flow_rate
end
def dishwashing_total
dishwasher_rate * dishwasher_multiplier * dishwasher_method
end
def laundry_total
laundry_rate * laundry_method * laundry_multiplier
end
def lawn_total
lawn_rate * lawn_multiplier * lawn_size * xeriscaping
end
def swimming_total
(swimming_pool / 365) + (swimming_months * 1000 / 365)
end
def carwash_total
carwash_rate * carwash_multiplier * carwash_method
end
end
class WaterusagesController < ApplicationController
...
def create
#user = User.find(params[:user_id])
#waterusage = #user.waterusage.create(waterusage_params)
redirect_to user_path(#user)
end
...
end
First of all prefix every ':' inside of create with a 'params[' and suffix ']', then change every '_' with a '#'.
It would be like this:
_powerTotal = :statewater * :percent_statewater / 100
turns into
#powerTotal = params[:statewater].to_i * params[:percent_statewater].to_i /100
Like that,
:individual_total = :household_total / :household_size
turns into
#individual_total = params[:household_total].to_i / params[:household_size].to_i
Also you're doing nothing with your calculations, they are just floating around, as it is, you can't even invoke them from your view.
If you want it to save on your waterusage object that relates to an user the individual_total attributes it would be;
#waterusage = #user.waterusage.create(waterusage_params, individual_total: #individual_total).

Error with virtual tree program ruby

I have just started learning ruby and have made this program following a tutorial.
I keep getting an error when trying to run and can't find an answer.
The program is suppose to be able to pick fruit, count the fruit, give the height and grow.
C:\Sites\testfolder>ruby orangetree.rb
orangetree.rb:2:in `initialize': wrong number of arguments (1 for 0) (ArgumentEr
ror)
from orangetree.rb:51:in `new'
from orangetree.rb:51:in `<class:OrangeTree>'
from orangetree.rb:1:in `<main>'
C:\Sites\testfolder>
Here is the program
class OrangeTree
def initialize
#age = 0
#tall = 0
#fruit = 0
puts 'You have planted a new tree!'
def height
puts 'The tree is ' + #tall.to_s + 'foot tall.'
end
def pickAFruit
if #fruit <= 1
puts 'There is not enough fruit to pick this year.'
else
puts 'You pick an orange from the tree.'
#fruit = #fruit - 1
end
end
def countOranges
puts 'The tree has ' + #fruit.to_s + 'pieces of fruit'
end
def oneYearPasses
#age = #age + 1
#tall = #tall + 3
#fruit = 0
if dead?
puts 'The Orange Tree Dies'
exit
end
if #age > 2
#fruit = #age*10
else
#fruit = 0
end
end
private
def dead?
#age > 5
end
end
tree = OrangeTree.new 'tree'
command = ''
while command != 'exit'
puts 'please enter a command for the virual tree'
command = gets.chomp
if command == 'tree height'
tree.height
elsif command == 'pick fruit'
tree.pickAFruit
elsif command == 'wait'
tree.oneYearPasses
elsif command == 'count fruit'
tree.countOranges
elsif command == 'exit'
exit
else
puts 'Cant understand your command, try again'
end
end
end
Can anybody help?
You have some syntax errors. I have fixed them below. The syntax errors were:
You had an extra end on the last line (for closing the class declaration)
You were passing an argument to OrangeTree.new ("tree") that was unexpected. This is what the wrong number of arguments (1 for 0) error message in your question is referring to.
You were missing an end to close your initialize method declaration (after the puts 'You have planted a new tree!' line)
I have also fixed the indentation, which makes the code more readable and much easier to spot syntax errors like this.
class OrangeTree
def initialize
#age = 0
#tall = 0
#fruit = 0
puts 'You have planted a new tree!'
end
def height
puts 'The tree is ' + #tall.to_s + 'foot tall.'
end
def pickAFruit
if #fruit <= 1
puts 'There is not enough fruit to pick this year.'
else
puts 'You pick an orange from the tree.'
#fruit = #fruit - 1
end
end
def countOranges
puts 'The tree has ' + #fruit.to_s + 'pieces of fruit'
end
def oneYearPasses
#age = #age + 1
#tall = #tall + 3
#fruit = 0
if dead?
puts 'The Orange Tree Dies'
exit
end
if #age > 2
#fruit = #age*10
else
#fruit = 0
end
end
private
def dead?
#age > 5
end
end
tree = OrangeTree.new
command = ''
while command != 'exit'
puts 'please enter a command for the virual tree'
command = gets.chomp
if command == 'tree height'
tree.height
elsif command == 'pick fruit'
tree.pickAFruit
elsif command == 'wait'
tree.oneYearPasses
elsif command == 'count fruit'
tree.countOranges
elsif command == 'exit'
exit
else
puts 'Cant understand your command, try again'
end
end

Any option to post-process returned value in long conditional, other than setting variables for each statement?

def some_method(x)
if x == 1
date = Date.today
elsif x == 5
date = Date.today + 2
else
date = Date.today - 2
end
date + 20
end
For visual clarity, is it possible somehow to omit date = for each statement and catch whatever the returned value is from the conditional and add 20 to it?
(The code is for example purpose, my own code has 10 if-statements.)
def some_method(x)
date = if x == 1
Date.today
elsif x == 5
Date.today + 2
else
Date.today - 2
end
date + 20
end
If you have 10 if statements it is probably better to refactor code using case-when like this:
def some_method(x)
date = case x
when 1; Date.today
when 5; Date.today + 2
else; Date.today - 2
end
date + 20
end

calculate number in views ruby on rails

I would like to calculate mid test and final test mark into a grade. E.g.:
mid test = 80 , final test = 80
(midtest + finaltest)/2 >=80
grade = "A"
Is it possible to do an if condition in views and insert into database? Something like:
if (midtest + finaltest) / 2 >= 80
grade = "A"
elsif (midtest + finaltest)/2 >= 70 and < 80
grade = "B"
elsif (midtest + finaltest) /2 >= 60 and < 70
grade = "C"
So that in views we don't need a text_field for grades and so that the calculation is automatically inserted into the database.
This is the solution
Controller
def create
#nilai = Nilai.new(params[:nilai])
#nilai.get_grade
respond_to do |format|
if #nilai.save
format.html { redirect_to #nilai, notice: 'Nilai was successfully created.' }
format.json { render json: #nilai, status: :created, location: #nilai }
else
format.html { render action: "new" }
format.json { render json: #nilai.errors, status: :unprocessable_entity }
end
end
end
Model
class Nilai < ActiveRecord::Base
attr_accessible :grade, :id_makul, :id_mhs, :id_nilai, :uas, :uts
def get_grade
#calculate = (self.uas + self.uts)/2
if #calculate >= 80
self.grade = "A"
elsif #calculate >=70 and #calculate < 80
self.grade = "B"
elsif #calculate >=60 and #calculate <70
self.grade = "C"
elsif #calculate >=50 and #calculate <60
self.grade = "D"
else
self.grade = "E"
end
end
end
Still guessing what you really want and why you think you have to do it in the view...
As I said above, Views should be used solely for code that displays data that already exists. Code that inserts things into the database is for your models and controllers.
I suggest either:
1) you create a method on your model called "grade" eg:
def grade
if (midtest + finaltest) / 2 >= 80
return "A"
elsif (midtest + finaltest)/2 >= 70 and < 80
return "B"
elsif (midtest + finaltest) /2 >= 60 and < 70
return "C"
else
return "F"
end
end
now, you can call this method from your view eg:
Grade: <%= #my_model.grade %>
Note that this method does not insert it into the database.
OR
2) you create a method as above on a before_save callback
eg lets say you're storing it into the "grade" column in the db:
class MyModel < ActiveRecord::Base
before_create :calculate_grade
def calculate_grade
if (midtest + finaltest) / 2 >= 80
self.grade = "A"
elsif (midtest + finaltest)/2 >= 70 and < 80
self.grade = "B"
elsif (midtest + finaltest) /2 >= 60 and < 70
self.grade = "C"
else
self.grade = "F"
end
end
end
And now whenever your model gets saved, the grade gets re-calculated from the test scores and saved into the db alongside.
so you can use "grade" in your views as above, but it's coming from the database column
Grade: <%= #my_model.grade %>
technically you could do almost anything in views, after all view is also a ruby file. So you could have conditions, DB connections etc..
BUT, Its not a good practise to have your logic in your views. Always try to have your view to display only. All the processing logic should be in Models
Idea is, Fat models, thin controllers are views are only for presentation. So in your case try and see at least to get your login in to a helper method.
One more thing I notice, you could have this line
(midtest + finaltest) / 2
to
average_marks = (midtest + finaltest) / 2
and use average_marks, in other places, as its more DRY (Dont Repeat Yourself)
HTH :)

Resources