Moving logic from controller to model in rails 3? - ruby-on-rails

I've been building a contest application, and I can easily tell I've been putting wayyyy too much logic in the controller. How can I go about switch this type of logic to the model? (whats important here isn't the logic itself - its far from finished - I'm just trying to understand how to move it out of the controller).
Controller:
def create
#person = Person.new(params[:person])
#yournum = rand(100)
#day = Day.find_by_id(1)
#prereg = Prereg.find_by_email(#person.email)
if #preg != nil
#person.last_name = #prereg.name
end
if #day.number == 1
if #yournum <= 25
#person.prize_id = 2
elsif #yournum > 25 && #yournum <=50
#person.prize_id = 1
elsif #yournum > 51 && #yournum <=75
#person.prize_id = 3
elsif #yournum > 76 && #yournum <=100
#person.prize_id = 4
end
elsif #day.number == 2
if #yournum <= 25
#person.prize_id = 2
elsif #yournum > 25 && #yournum <=50
#person.prize_id = 1
elsif #yournum > 51 && #yournum <=75
#person.prize_id = 3
elsif #yournum > 76 && #yournum <=100
#person.prize_id = 4
end
elsif #day.number == 3
if #yournum <= 50
#person.prize_id = 2
elsif #yournum > 51 && #yournum <=90
#person.prize_id = 1
elsif #yournum > 91 && #yournum <= 95
#person.prize_id = 3
elsif #yournum > 96 && #yournum <=100
#person.prize_id = 4
end
end
#person.save
redirect_to #person
end
Model:
class Person < ActiveRecord::Base
belongs_to :prize
end
Thanks!
Elliot

Indeed, that's a pretty ugly controller. As you say, the solution is easy: move all the logic to the model:
def create
#person = Person.new(params[:person])
#person.set_price
if #person.save
redirect_to #person
else
flash[:error] = ...
render :action => 'new'
end
end
class Person
def set_price
# your logic here
end
end
Note that:
Controller: you need to check if #person was actually saved (maybe some validation failed).
Model: If a person has always to be assigned a price on creation, then use a callback (before_validation, for example). Otherwise, call it from the controller as shown the code above.

class PersonsController < ApplicationController
respond_to :html
def create
#person = Person.new(params[:person])
if #person.save
respond_with #person
else
flash[:error] = 'Render error'
render :action => :new
end
end
end
class Person
before_create :method_name
def method_name
#Put whatever you want to happen before creation here
end
end

Related

NoMethodError in controller

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

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

Rails - Simple Loop Not Working

In my controller I am trying to do a bulk insert into a table, in my first attempt it works but the names somehow get mangled as the following: (loop runs 24 times which is what I want)
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11
test-port-name-0-1-2-3-4-5-6-7-8-9-10
test-port-name-0-1-2-3-4-5-6-7-8-9
test-port-name-0-1-2-3-4-5-6-7-8
test-port-name-0-1-2-3-4-5-6
test-port-name-0-1-2-3-4-5-6-7
test-port-name-0-1-2-3-4-5
test-port-name-0-1-2-3-4
test-port-name-0-1-2
test-port-name-0-1-2-3
test-port-name-0
test-port-name-0-1
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22
test-port-name-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23
instead of test-port-name-0 .... test-port-name-23
def bulk_port_import
if request.post?
#attempt create
count = 0
for i in 1..session[:no_ports]
params[:dp][:name] = params[:dp][:name] + '-' + count.to_s
#dp = DevicePort.create params[:dp]
count = count + 1
end
end
#success = "Saved." if #dp.valid?
#error = ""
#dp.errors.each_full {|e| #error += e + ", "}
redirect_to '/device/update/' + params[:dp][:device_id]
end
Different attempt:
def bulk_port_import
if request.post?
#attempt create
i = 0
while i < session[:no_ports] do
params[:dp][:name] = params[:dp][:name] + '-' + i.to_s
#dp = DevicePort.create params[:dp]
i++
end
end
session.delete(:no_ports)
#success = "Saved." if #dp.valid?
#error = ""
#dp.errors.each_full {|e| #error += e + ", "}
redirect_to '/device/update/' + params[:dp][:device_id]
end
but with this I get syntax error, unexpected kEND and I can't see what I'm doing wrong in either case, it's probably something stupid, again.
Its because you are changing params[:dp][:name] in the loop
def bulk_port_import
if request.post?
#attempt create
count = 0
for i in 1..session[:no_ports]
dp_name = params[:dp][:name] + '-' + count.to_s
#dp = DevicePort.create(params[:dp].merge(:name => dp_name))
count = count + 1
end
end
#success = "Saved." if #dp.valid?
#error = ""
#dp.errors.each_full {|e| #error += e + ", "}
redirect_to '/device/update/' + params[:dp][:device_id]
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 :)

Refactor this if else between block of Ruby/Rails code

Here's the code. Basically, the user selects a billing day (the 1st of each month, or the 15th of each month). The start_date is when the "contract" begins, the expire_date is when it expires.
So, if today is the 3rd, and they want to be billed on the 15th, then simply go to the 15th day of the current month. However, if today is the 3rd and they want to be billed on the first, then get the 1st day of next month... etc.
if params[:billing_day] == 1 && start_date.day > 1
expire_date = start_date.at_beginning_of_month.next_month
elsif params[:billing_day] == 15 && start_date.day < 15
expire_date = start_date.change(:day => 15)
elsif params[:billing_day] == 15 && start_date.day > 15
expire_date = start_date.at_beginning_of_month.next_month.change(:day => 15)
else
expire_date = start_date.change(:day => params[:billing_day])
end
It just seems crazy, surely it can be simplified in Rails. Thanks!
I would write something along the lines of
expire_date = start_date.change(:day => params[:billing_day])
if expire_date <= start_date
expire_date += 1.month
end
You'd need to validate that a valid billing day was picked before this
Came up with this. But not sure if it's good idea
expire_date = if params[:billing_day] == 1 && start_date.day > 1
start_date.at_beginning_of_month.next_month
elsif params[:billing_day] == 15 && start_date.day < 15
start_date.change(:day => 15)
elsif params[:billing_day] == 15 && start_date.day > 15
start_date.at_beginning_of_month.next_month.change(:day => 15)
else
start_date.change(:day => params[:billing_day])
end
And what if params[:billing_day] == 15 && start_date.day == 15 ? What should happen?
Came up with this also, but it behaves a little different in the case of start_date.day == 15
expire_date = if params[:billing_day] == 1 && start_date.day > 1
start_date.at_beginning_of_month.next_month
elsif params[:billing_day] == 15
if start_date.day < 15
start_date.change(:day => 15)
else
start_date.at_beginning_of_month.next_month.change(:day => 15)
end
else
start_date.change(:day => params[:billing_day])
end
Also notice that in Rails it's good practice to put logic in models, and since you are using params[] you are doing it in the controller.
class ExpireDate
def initialize(billing_day, start_date)
#billing_day = billing_day
#start_date = start_date
end
def expires_on
if billing_day == 1
return billing_on_1st
elsif billing_day == 15
return billing_on_15th
else
raise "Unknown billing_day"
end
end
def billing_on_1st
if #start_date.day > 1
return #start_date.at_beginning_of_month.next_month
else
return #start_date.change(:day => #billing_day)
end
end
def billing_on_15th
if #start_date.day < 15
return #start_date.change(:day => 15)
else
return #start_date.at_beginning_of_month.next_month.change(:day => 15)
end
end
end
expire_date = ExpireDate.new(params[:billing_day], start_date).expires_on

Resources