In my rails app, I want to check an amount sent via a form before saving it to the DB. If the amount is too big, I want to set a boolean variable "confirmed" to false. Otherwise, its confirmed and true.
I entered this in my model:
# if amount is too big, set to unconfirmed
before_save do
if self.amount > 9999
self.confirmed = false
else
self.confirmed = true
end
end
Controller action (was scaffolded):
def create
#statement = Statement.new(statement_params)
respond_to do |format|
if #statement.save
format.html { redirect_to thankyou_path, notice: 'Successfully created.' }
format.json { render action: 'show', status: :created, location: #statement }
else
format.html { render action: 'new' }
format.json { render json: #statement.errors, status: :unprocessable_entity }
end
end
end
Testing this results in the following:
- if the amount is < 9999, the form gets saved, all good.
- if the amount is > 9999, the form does not get saved. It simply stays on the same page and nothing happens. No error message and nothing to see in the log except the fact that the data did not get entered into the database.
What do I do wrong?
It's because if amount is greater than 9999, the value returned from block is false (from self.confirmed = false line) - and if block (or method) passed into before_save returns false, ActiveRecord stops saving the record. So the simple solution is to add true that would be returned:
before_save do
if self.amount > 9999
self.confirmed = false
else
self.confirmed = true
end
true
end
The relevant piece of documentation for reference:
If a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last.
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#module-ActiveRecord::Callbacks-label-Canceling+callbacks
Related
Building on the helpful and working solution presented here, I'm trying to fix my update callback as well.
Problem is, the specific unit that I'm trying to extract data from is always the old cached version, even though this callback is triggered by a successful update action.
// callback triggered by the update action
$('.best_in_place').bind("ajax:success", function () {
...
console.log(unit.duration);
// which is exactly the same as
console.log(<%= Unit.find(unit.id).unit_users.pluck(:duration).sum %>);
// and both print the OLD duration val instead of the updated val which is in the database
});
and the unit_users_controller code...
def update
#unit = #unituser.unit
respond_to do |format|
if #unituser.update(unit_user_params)
#unit.reload
logger.info('-----------------------------------------------------------------')
logger.info('#unit.duration in the controller is ' + #unit.duration.to_s) # which is the correct value
logger.info('-----------------------------------------------------------------')
gon.unit_duration = #unit.duration # an experiment which didn't work for me
format.json {respond_with_bip(#unituser) }
else
# format.html { render :action => 'edit' }
format.json { respond_with_bip(#unituser) }
end
end
end
I've tried several versions of unit.reload, and nothing helps. Maybe I was putting it in the wrong place?
I did this one sometime ago here is my code, maybe it will help you:
Javascript:
$(document).ready(function() {
$('.price_bind').bind("ajax:success", function (event, data, status, xhr) {
var parsed_data = jQuery.parseJSON(data);
$(this).text(parsed_data.newprice);
$(this).parentsUntil('body').find(".totalpricep span").text(parsed_data.totalprice);
});
}
View:
<%= best_in_place detail, :price, :classes => 'price_bind', :path => purchase_detail_path(#purchase, detail)%>
Controller:
def update
respond_to do |format|
if #detail.update_attributes(params[:detail])
#n=#detail.mk_bal
#r=false
if #detail.purchase != nil
#p=#detail.purchase.totalprice
if params[:detail]['status'] && #purchase.step==1
#remdet = #purchase.details.where(:step => 1, :status => false)
if #remdet.empty?
#purchase.update_attribute(:step, 2)
#r=true
end
end
else
#p=nil
end
format.html { redirect_to #detail, notice: 'Detail was successfully updated.' }
format.json { render :json => {:newprice => #n, :totalprice => #p, :newstatus => #detail.status, :refresh => #r}}
else
format.html { render action: "edit" }
format.json { render json: #detail.errors, status: :unprocessable_entity }
end
end
end
This isn't about caching. Your Ruby code is evaluated server-side, before the JavaScript is ever send to the client, and it's only evaluated once, long before the AJAX request can happen.
The client never sees this line:
console.log(<%= Unit.find(unit.id).unit_users.pluck(:duration).sum %>);
All the client will see is something like:
console.log(32); // or whatever the sum is
You cannot use <%= %> here. That will always give you the original value. Instead, you need to send the new value to the client in response to the AJAX request.
I've been at this for a while and need a smarter person's opinion. I'm trying to check the passcode I have in my database matches the passcode the user enters in order to delete a record. I believe that I should be doing this in the model, and I am refactoring to use the before_destroy method. However I can't even get the before_destroy method to execute when I click on the delete button I made. The controller does execute the destroy method though.
View
<%= button_to('Destroy', #dish, method: "delete") %>
Model - the puts passcode_check? is never called from what I see
class Dish < ActiveRecord::Base
before_destroy :passcode_check?
validates :username, presence: true
validates :passcode, presence: true
validates :guests, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
private
def passcode_check?
puts "passcode_check?"
if #dish.passcode != params[:pass]
#dish.errors.add( :base, 'Unable to delete record; Reason: Passcodes did not match.')
return false
else
#dish.errors.add( :base, 'test.')
return false
end
end
end
Controller - this method is influx as I want to validate in the model
def destroy
if #dish.passcode == params[:pass]
#dish.destroy unless #dish.errors
respond_to do |format|
format.html { redirect_to dishes_url, notice: 'Record delete.' }
format.json { head :no_content }
end
else
respond_to do |format|
format.html { redirect_to #dish, action: 'show', notice: 'Unable to delete record; Reason: Passcodes did not match.' }
format.json { render json: #dish.errors, status: :unprocessable_entity }
end
end
end
In your controller you're using #dish.errors which will always return an ActiveModel::Errors object and therefore be truthy. So the unless #dish.errors statement modifier never lets #dish.destroy run and consequently neither will your callback. Change it to:
#dish.destroy if #dish.errors.empty?
And that should be it. Although it doesn't make much sense to check for errors yet since no validations have even run. Just call #dish.destroy and let your before_destroy callback halt the deletion by returning false and, conversely, let the deletion happen by returning true.
I'm making an ajax call to this method
def check_solution
puzzle = Puzzle.find(params[:id])
solved = puzzle.grid.solution == params[:solution].to_s
solved_before = params[:solved_before]
puts !solved_before
if(solved && !solved_before)
Puzzle.find(params[:id]).increment!(:times_solved, by =1)
end
respond_to do |format|
response = { :status => "ok", :message => "Success!", :html => solved}
format.json { render json: response }
end
end
The parameters going in from my local server are
Parameters: {"solution"=>"0001000010110011001100000", "solved_before"=>"false", "id"=>"3758"}
Why, when I print out !solved_before with puts, does it say false instead of true?
That's because solved_before comes in as a string and not as a boolean.
There is model call Event which contains following attributes
start_at,
end_at,
details,
trainer
This is my normal create method which generated my scaffold command
def create
#event = Event.new(params[:event])
respond_to do |format|
if #event.save
format.html { redirect_to(#event, :notice => 'Event was successfully created.') }
format.xml { render :xml => #event, :status => :created, :location => #event }
else
format.html { render :action => "new" }
format.xml { render :xml => #event.errors, :status => :unprocessable_entity }
end
end
end
and I need to modify this as follows when particular event object have date gap between stat_at and end_at more than 6 I need to save that event as two events. First event will started at original started date and end date should be middle date of the event and other data are same. But in second event it should be start date as middle date and end date should have original end date. Can some one explain how could I done this???
After the line if #event.save you can create another Event record like this
Please note this code is untested.
if (#event.end_at - #event.start_at) > 6
event2 = Event.new
event2.start_at = something
event2.end_at = anotherthing
event2.save
end
As you are going to do some logic, you can write a method to perform your logic and call that method from controller. Better keep the method in Event model to satisfy good practice of Fat Model and Lean Controller.
def self.create_event(ev_params)
status = false
if event.end_dat - event.stat_dat >6
# create two events
event1 = Event.new(ev_params)
mid_date = event1.stat_at + ((event.end_dat - event.stat_dat)/2).days
event1.end_at = mid_date
event2 = Event.new(ev_params)
event2.stat_at = mid_date
status = event1.save'
status = event=2.save'
else
status = Event.create(ev_params)
end
status
end
And call this method from controller Event.create_event in controller #event.save.
This is not tested code but I hope you can easily get from above code.
Here's the model file:
class ProfileTag < ActiveRecord::Base
def self.create_or_update(options = {})
id = options.delete(:id)
record = find_by_id(id) || new
record.id = id
record.attributes = options
puts "record.profile_id is"
puts record.profile_id
record.save!
record
end
end
This gives me the correct print out in my log. But it also says that there's a call to UPDATE that sets profile_id to NULL. Here's some of the output in the log file:
Processing ProfilesController#update (for 127.0.0.1 at 2010-05-28 18:20:54) [PUT]
Parameters: {"commit"=>"Save", "profile"=>{"id"=>"2", "password_confirmation"=>"", "username"=>"user2", "first_name"=>"user2_first", "password"=>"", "last_name"=>"user2_last"}, "authenticity_token"=>"...", "tag"=>"1", "id"=>"2"}
?[4;36;1mProfileTag Create (0.0ms)?[0m ?[0;1mINSERT INTO `profile_tags`
(`reputation_value`, `updated_at`, `tag_id`, `id`, `profile_id`, `created_at`) VALUES(0, '2010-05-29 01:20:54', 1, NULL, 4, '2010-05-29 01:20:54')?[0m
?[4;35;1mSQL (2.0ms)?[0m ?[0mCOMMIT?[0m
?[4;36;1mSQL (0.0ms)?[0m ?[0;1mBEGIN?[0m
?[4;35;1mSQL (0.0ms)?[0m ?[0mCOMMIT?[0m
?[4;36;1mProfileTag Load (0.0ms)?[0m ?[0;1mSELECT * FROM `profile_tags` WHERE (`profile_tags`.profile_id = 4) ?[0m
?[4;35;1mSQL (1.0ms)?[0m ?[0mBEGIN?[0m
?[4;36;1mProfileTag Update (0.0ms)?[0m ?[0;1mUPDATE `profile_tags` SET profile_id = NULL WHERE (profile_id = 4 AND id IN (35)) ?[0m
I'm not sure I understand why the INSERT puts the value into profile_id properly, but then it sets it to NULL on an UPDATE.
[Edit]
In ProfileController:
def update
#...stuff. Set tags array.
save_tags(tags) #These tags are correct. Verified by printouts before and after this call.
respond_to do |format|
if #profile.update_attributes(params[:profile])
flash[:notice] = 'Profile was successfully updated.'
#format.html { redirect_to(#profile) }
format.html { redirect_to :action=>'show' }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #profile.errors, :status => :unprocessable_entity }
end
end
end
def save_tags(tags)
profile = find_profile #finds the correct profile. And I confirm that it exists with a printout
tags.each do |t|
ProfileTags.create_or_update(:profile_id => profile.profile_id, :tag_id => t.id)
end
end
If you need more specifics, please let me know. I'm thinking that the save functionality does many things other than INSERTs into the database, but I don't know what I need to specify so that it will properly set profile_id.
Look at the line:
ProfileTags.create_or_update(:profile_id => profile.profile_id, :tag_id => t.id)
I believe you want to pass profile.id, and not profile.profile_id (which is probably null).
save! itself should't do this.
Maybe your problem is the name of the method. ActiveRecord::Base already have a method named create_or_update (see http://github.com/rails/rails/blob/2-3-stable/activerecord/lib/active_record/base.rb#L2913) which is called by save! - maybe replacing it causes this weird problem.
Try changing the name of your method to something else, it might help.
You aren't passing the id attribute to the create_or_update method in the first place, so you don't need to call it, just call create instead, like so:
def save_tags(tags)
profile = find_profile #finds the correct profile. And I confirm that it exists with a printout
tags.each do |t|
ProfileTag.create(:profile_id => profile.profile_id, :tag_id => t.id)
end
end