This seems like a dumb question, but how do I update a database field from a model method? The incident.incident_number value is displayed on all the forms and emails, but is NULL in the database:
incident.rb
validate :incident_number, :on => :save
def incident_number
(self.created_at.strftime("%Y") + self.created_at.to_date.jd.to_s + self.id.to_s).to_i
end
incidents_controller.rb
def create
#incident = Incident.new(incident_params)
#incident.user_id = current_user.id
#incident.state_id = 1
respond_to do |format|
if #incident.save
IncidentMailer.new_incident_notification(#incident).deliver
format.html { redirect_to my_incidents_path, notice: 'Incident was successfully created.' }
format.json { render action: 'show', status: :created, location: #incident }
else
format.html { render action: 'new' }
format.json { render json: #incident.errors, status: :unprocessable_entity }
end
end
end
Your current method doesn't persist to the database, it just runs the logic every time the method is called.
There are a number of ways to do this - either via write_attributes (works well in before_save)
before_save :update_incident_number, if: Proc.new { conditional_logic_here }
...
def update_incident_number
write_attribute(:incident_number, new_value)
end
Or, use update_attribute(s)
after_commit :update_incident_number, if: Proc.new { conditional_logic }
...
def update_incident_number
self.update_attribute(:incident_number, new_value)
end
There are a few ways to skin this cat, try a few and see which you prefer based on what callbacks are triggered, and how they deal with your record / changed properties.
Related
I got a simple app where users can create projects and timetrackers which belong_to the projects and are basically timestamps. timetrackers have a start_time and an end_time(both :datetime), now i want to calulate the duration between the two values and save it into timespan which is :float.
for this i got a set_timespan action in my timetrackers_controller
def set_timespan
#job = Job.find(params[:job_id])
#timetracker = Timetracker.find(params[:id])
#timetracker.timespan = (#timetracker.end_time - #timetracker.start_time).round / 3600
end
The Create action
def create
#job = Job.find(params[:job_id])
#timetrackers = #job.timetrackers.new(timetracker_params)
#timetrackers.user_id = current_user.id
#timetrackers.job_id = #job.id
#set_timespan
respond_to do |format|
if #timetrackers.save
format.html { redirect_to #job, notice: 'Timestamps created successfully.' }
format.json { render :show, status: :created, location: #job }
else
format.html { redirect_to new_timetracker_path, notice: "Please fill out the form." }
format.json { render json: #job.errors, status: :unprocessable_entity }
end
end
end
when i try to shoot the action in my create action just before #timetrackers.save, i get a Couldn't find Timetracker without an ID Error. Also I get redirected to jobs/3/timetrackers. Why is the ID not found and how can i properly trigger an action in my controller to calculate the timespan? Later i want to sum up the timespans.
You'll still have access to the #timetrackers instance variable, so as a simple solution:
def set_timespan
#job = Job.find(params[:job_id])
#timetrackers.timespan = (#timetracker.end_time - #timetracker.start_time).round / 3600
end
I'd take this a step further and remove the method altogether, as #job is also available and just use the pertinent line directly in the create action:
def create
# ...
#timetrackers.job_id = #job.id
# v here v
#timetrackers.timespan = (#timetrackers.end_time - #timetrackers.start_time).round / 3600
respond_to do |format|
# ...
end
As a final, perhaps ideal solution, you can use a before_create callback in your Timespan model:
# timespan.rb
before_create -> { self.timespan = (end_time - start_time).round / 3600 }
That way, your timespan will be set automatically as it's created.
I realize you have an answer you already like, but...
Another approach would be to groom your timetracker_params so that they include all the appropriate data using a method like new_timetracker_attributes. Something, perhaps, like:
def create
#job = Job.find(params[:job_id])
#timetrackers = #job.timetrackers.new(new_timetracker_attributes)
respond_to do |format|
if #timetrackers.save
format.html { redirect_to #job, notice: 'Timestamps created successfully.' }
format.json { render :show, status: :created, location: #job }
else
format.html { redirect_to new_timetracker_path, notice: "Please fill out the form." }
format.json { render json: #job.errors, status: :unprocessable_entity }
end
end
end
private
def new_timetracker_attributes
timetracker_params.merge(
user_id: current_user.id,
timespan: ((end_time-start_time).round/3600)
)
end
def timetracker_params
params.require(:timetracker).permit(:start_time, :end_time)
end
def start_time
timetracker_params[:start_time]
end
def end_time
timetracker_params[:end_time]
end
There are many ways you could groom your timetracker_params and this is just one idea.
The point is, you have all of your new instance attribute set up going on in one place instead of smeared in various places across your application. IMO, it makes it a wee bit easier to understand what is going on and debug if things go pear shaped.
I have a model like this:
class Url < ApplicationRecord
validates ...
before_create :generate_number
def generate_number
self.number = a random value
end
end
and a create() method in controller:
def create
#url = Url.new(url_params)
respond_to do |format|
if #url.save
format.html { redirect_to #url, notice: 'Url was successfully created.' }
format.json { render :show, status: :created, location: #url }
else
format.html { render :new }
format.json { render json: #url.errors, status: :unprocessable_entity }
end
end
end
My DB only have two fields: given_url and number. Now, when I go to the new page, there's 2 input form for given_url and number. But I want number take the value from generate_number, not from the form. How can I do that?
Or more specific, is there a way to make the generate_number method to overrides user's input after the app already receive value from user's input?
You can simply restrict the input from user by using strong params
def url_params
params.require(:url).permit(:other, :params)
end
Well, I have found a way to fix this. As #ts mentioned in the question's comment, I changed before_create :generate_number to after_create :generate_number and added self.save to the end of generate_number method:
def generate_number
...
self.number = some number
self.save
end
I have a very beginners understanding of rails and ruby and I keep
getting stuck. If anyone could please point out my where I'm going wrong, that would be great! Or else, is there an easier way to validate the database before I allow an appointment? I don't want double bookings.
I am making an appointment booking app and I have a very basic design. I
have created an appointments scaffold with name:string phone:string
email:string numpeople:integer date:date timeslot:string.
In the view for creating a new appointment I have stated that appointment 1 is
9-11am, appointment 2 is 12-2pm, appointment 3 is 3-5pm and appointment
4 is 5 - 7pm. The user is asked to enter 1,2,3 or 4.
When the user clicks on "make appointment" I'm trying to interrupt the
appointments controller (create method) so that I can check if the date
&& timeslot are nil. if that is the case, the system should continue on
to create the appointment, if not then I want to redirect to somewhere
else. I have created a method called isValid? in the model (See below)
I think the method is correct as the system is getting as far as the
redirect. Tclass Appointment < ActiveRecord::Base
class Appointment < ActiveRecord::Base
def isValid?
taken = Appointment.where("date = ? && timeslot = ?", date, timeslot)
save unless taken
end
end
The problem is, it keeps redirecting to the page I told it to
go to if it's not saved(the homepage or root_path). (Also the
appointments are not saving).
appointments controller create method:
def create
valid = #appointment = Appointment.new(appointment_params).isValid?
respond_to do |format|
if valid
format.html { redirect_to new_appointment_path, notice: 'Appointment was
successfully created.' }
format.json { render :show, status: :created, location: #appointment }
else
format.html { redirect_to appointments_path, notice: 'That appointment
is not available, please choose again' } # this redirect works with no
notice
format.js { render json: #appointment.errors, status:
:unprocessable_entity }
end
end
end
Full appointments controller class: (In case I've missed something)
class AppointmentsController < ApplicationController
before_action :set_appointment, only: [:show, :edit, :update, :destroy]
def index
#appointments = Appointment.all
end
def show
end
def new
#appointment = Appointment.new
end
def edit
end
def create
valid = #appointment = Appointment.new(appointment_params).isValid?
respond_to do |format|
if valid
format.html { redirect_to new_appointment_path, notice: 'Appointment was
successfully created.' }
format.json { render :show, status: :created, location: #appointment }
else
format.html { redirect_to appointments_path, notice: 'That appointment
is not available, please choose again' } # this redirect works with no
notice
format.js { render json: #appointment.errors, status:
:unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #appointment.update(appointment_params)
format.html { redirect_to #appointment, notice: 'Appointment was
successfully updated.' }
format.json { render :show, status: :ok, location: #appointment }
else
format.html { render :edit }
format.json { render json: #appointment.errors, status:
:unprocessable_entity }
end
end
end
def destroy
#appointment.destroy
respond_to do |format|
format.html { redirect_to appointments_url, notice: 'Appointment was
successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_appointment
#appointment = Appointment.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list
through.
def appointment_params
params.require(:appointment).permit(:name, :phone, :email, :numpeople,
:date, :timeslot)
end
end
I think your method should be something like:
class Appointment < ActiveRecord::Base
def isValid?
taken = Appointment.where("date = ? && timeslot = ?", date, timeslot)
save unless taken.present?
end
end
Let me know if that works.
I have a problem with simple model validation.
Entering materials without SKU is getting me error message:
NoMethodError in Materials#create undefined method `empty?' for
nil:NilClass
material.rb:
class Material < ActiveRecord::Base
validates :sku, :presence => true
end
materials_controler.rb (create part only):
# POST /materials
# POST /materials.json
def create
#material = Material.new(material_params)
respond_to do |format|
if #material.save
format.html { redirect_to #material, notice: 'Material was successfully created.' }
format.json { render :show, status: :created, location: #material }
else
format.html { render :new }
format.json { render json: #material.errors, status: :unprocessable_entity }
end
end
end
Your #units instance variable is nil in create action. You should set it the same way as you do in new or edit actions after your records fails validation.
Define #units also in your create method
I want to check to see if a feed I retrieve based on a user-specified url is nil, but I can't seem to find a good way to handle this.
I feel the best way to handle it would be to set a custom ActiveRecord error upon checking via if statement like I've added in the code below. But then how do I force the save to fail?
The code needs access to the feed_url so that I can validate it's either nil or returns 404 etc...
# POST /feeds
# POST /feeds.json
def create
#feed = Feed.new(params[:feed])
feed = Feedzirra::Feed.fetch_and_parse(#feed.feed_url)
if(feed) #what to do here?? the below doesn't work if feed returns as nil or fixnum
#feed.title = feed.title
#feed.author = feed.entries.first.author
#feed.feed_url = feed.feed_url
#feed.feed_data = feed
end
respond_to do |format|
if #feed.save
format.html { redirect_to reader_path, notice: 'Feed was successfully created.' }
format.json { render json: #feed, status: :created, location: #feed }
else
format.html { render action: "new" }
format.json { render json: #feed.errors, status: :unprocessable_entity }
end
end
end
You can use before_save callback in your model.
class Feed < ActiveRecord::Base
before_save :check_model
def check_model
if condition == True
#Execute true statement
else
# raise error
end
Thanks