Why isn't Rails deleting dependent objects? - ruby-on-rails

I have...
/app/models/input.rb:
class Input < ActiveRecord::Base
has_many :questions, :dependent => :destroy
after_commit :create_matching_questions
def create_matching_questions
#element_id = Element.all.select{|e| e.meta == true}.first.id
#standard_id = Standard.all.select{|s| s.meta == true}.first.id
#description = ["Does the site stock ", self.name, "?"].join
Product.all.each do |product|
#question = product.questions.find_or_create_by_element_id_and_standard_id_and_description!(#element_id, #standard_id, #description)
self.questions << #question
#question.fields.find_or_create_by_name("The site sells this product and it is in stock")
#question.fields.find_or_create_by_name("The site sells this product but it is not in stock")
#question.fields.find_or_create_by_name("The site does not sell this product")
#question.update_attributes :active => true
end
return true
end
end
/app/models/question.rb:
class Question < ActiveRecord::Base
belongs_to :input
after_commit :create_matching_surveys
def create_matching_surveys
if self.active == true
self.reload.product.reviews.each do |review|
review.competitors.each do |competitor|
(1..self.iterations).each do |iteration|
survey = competitor.surveys.find_or_create_by_question_id_and_iteration!(self.id, iteration)
survey.save
end
end
end
return true
else
self.destroy_matching_surveys
end
end
def destroy_matching_surveys
self.surveys.each do |survey|
survey.destroy if survey.question_id == self.id
end
return true
end
end
Why, then, do I get...
> #finance = Good.create! :name => "Finance"
=> #<Good id: 6, name: "Finance", created_at: "2013-06-13 02:56:20", updated_at: "2013-06-13 02:56:20">
> #super = Input.create! :name => "Superannuation"
=> #<Input id: 11, name: "Superannuation", mispelling: nil, typo: nil, created_at: "2013-06-13 02:56:28", updated_at: "2013-06-13 02:56:28">
> #first = #super.questions.first
=> #<Question id: 48, standard_id: 1, description: "Does the site stock Superannuation?", element_id: 2, condition_id: nil, blueprint_name: nil, blueprint_url: nil, additive: false, instructions: nil, created_at: "2013-06-13 02:56:41", updated_at: "2013-06-13 02:56:41", active: false, postscript: "<p>If you have any comments about this question or ...", iterations: 1, product_id: 1, precondition_id: nil, input_id: 11>
> #last = #super.questions.last
=> #<Question id: 60, standard_id: 1, description: "Does the site stock Superannuation?", element_id: 2, condition_id: nil, blueprint_name: nil, blueprint_url: nil, additive: false, instructions: nil, created_at: "2013-06-13 02:56:43", updated_at: "2013-06-13 02:56:43", active: false, postscript: "<p>If you have any comments about this question or ...", iterations: 1, product_id: 23, precondition_id: nil, input_id: 11>
> #super.destroy
=> #<Input id: 11, name: "Superannuation", mispelling: nil, typo: nil, created_at: "2013-06-13 02:56:28", updated_at: "2013-06-13 02:56:28">
> #super.destroyed?
=> true
> #first.destroyed?
=> false
> #last.destroyed?
=> false
Surely #first and #last should be destroyed automatically?

I had the same problem, solved it by :dependent => :delete_all instead of :dependent => :destroy.
:delete_all doesn't call the destroy method from your controller and delete data directly from your database.

Related

Callbacks in aasm gem and ActionMailer

Im learning ruby on rails and have a trouble with aasm callbacks and actionmailer.
I have a hotels model. Heres a code:
class Hotel < ActiveRecord::Base
include AASM
scope :approved_hotels, -> { where(aasm_state: "approved") }
has_many :comments
belongs_to :user, :counter_cache => true
has_many :ratings
belongs_to :address
aasm do
state :pending, initial: true
state :approved
state :rejected
event :approve, :after => :send_email do
transitions from: :pending, to: :approved
end
event :reject, :after => :send_email do
transitions from: :pending, to: :rejected
end
end
def send_email
end
end
As you see user has to get email when state of the hotel he added was changed. Heres what i wrote but its not THE solution cos user gets emails every time admin updates hotel with "pending" state.
class HotelsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show, :top5hotels]
def update
#hotel = Hotel.find(params[:id])
if #hotel.aasm_state == "pending"
#hotel.aasm_state = params[:state]
UserMailer.changed_state_email(current_user, #hotel.name,
#hotel.aasm_state).deliver
end
if #hotel.update_attributes!(params[:hotel])
redirect_to admin_hotel_path(#hotel), notice: "Hotel was successfully updated."
else
render "edit"
end
end
end
So i think i need to use callback but i dont know how to call
UserMailer.changed_state_email(current_user, #hotel.name,
#hotel.aasm_state).deliver
from the model.
I tried
UserMailer.changed_state_email(User.find(:id), Hotel.find(:name),
Hotel.find(aasm_state)).deliver
but that doesnt work.
Im really out of options and looking for any help.
Thanks!
UPDATE 1:
Thank to Amit Sharma! I`ve made these changes and now getting
NoMethodError: undefined method `email' for nil:NilClass
Looks like user object Im passing to changed_state_email() method is nill but I have no idea why.
Here is my mailer file aswell:
class UserMailer < ActionMailer::Base
default from: "localhost"
# Send email to user when hotels state change
def changed_state_email(user, hotel_name, current_state)
mail(to: user.email, subject: 'State of your hotel '+hotel_name+'has been
changed to '+current_state)
end
end
Here is a result of puts "====#{self.inspect}":
====#<Hotel id: nil, name: "CoolName", breakfast: nil, room_description: nil, price_for_room: 34, star_rating: 3, user_id: nil, address_id: nil, created_at: nil, updated_at: nil, average_rating: nil, photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, aasm_state: "approved">
F.====#
F.====#
UPDATE 2:
It returns user object. Output from the console:
1.9.3-p551 :006 > h = Hotel.find(1)
Hotel Load (0.4ms) SELECT "hotels".* FROM "hotels" WHERE "hotels"."id" = ? LIMIT 1 [["id", 1]]
=> #<Hotel id: 1, name: "QWERTYUI", breakfast: nil, room_description: nil, price_for_room: 44, star_rating: 4, user_id: 2, address_id: nil, created_at: "2015-05-30 22:55:01", updated_at: "2015-05-30 22:55:01", average_rating: nil, photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, aasm_state: "pending">
1.9.3-p551 :007 > h
=> #<Hotel id: 1, name: "QWERTYUI", breakfast: nil, room_description: nil, price_for_room: 44, star_rating: 4, user_id: 2, address_id: nil, created_at: "2015-05-30 22:55:01", updated_at: "2015-05-30 22:55:01", average_rating: nil, photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, aasm_state: "pending">
1.9.3-p551 :008 > h.user
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
=> #<User id: 2, name: "qwerty", email: "qweqweqweqwe#qwe.com", encrypted_password: "$2a$10$FG5xXb/9wYLcdsCrfJtuDOTsslyY8p.m0qkbP4a5OEvJ...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, admin: false, created_at: "2015-05-30 22:54:14", updated_at: "2015-05-30 22:54:14", comments_count: 0, hotels_count: 1>
You can Try this. I hope this will help you.
In Hotels Controller.
class HotelsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show, :top5hotels]
def update
#hotel = Hotel.find(params[:id])
if #hotel.pending?
if params[:state] == "approved"
#hotel.approved!
elsif params[:state] == "rejected"
#hotel.rejected!
end
end
if #hotel.update_attributes!(params[:hotel])
redirect_to admin_hotel_path(#hotel), notice: "Hotel was successfully updated."
else
render "edit"
end
end
end
In Hotel model.
def send_email
user = self.user
puts "====#{self.inspect}===#{user.inspect}"
UserMailer.changed_state_email(user, self.name,
self.aasm_state).deliver
end
Please revert back to me if you face any issue.

Validates_Overlap Gem Multiple Scopes Overwriting Eachother

I'm using Validates_Overlap Gem which can be found here: https://github.com/robinbortlik/validates_overlap
The essence is I have two rooms that can be booked. I want the validation to step in when the same room already has a CONFIRMED booking in the SAME room. It shouldn't throw me an error when the other room is booked, or if the same room is booked but hasn't been confirmed.
My code so far is as follows
validates :start_time, :end_time,
:overlap => {
:exclude_edges => ["starts_at", "ends_at"],
:scope => { "bookings.studio_id" => proc {|booking| booking.studio_id}} && { "bookings.is_confirmed" => proc {|booking| booking.is_confirmed == true}}
}, on: :update
This returns the following from my server:
Booking Exists (0.4ms) SELECT 1 AS one FROM "bookings" WHERE ((bookings.end_time IS NULL OR bookings.end_time >= '2014-10-23 20:00:00.000000') AND (bookings.start_time IS NULL OR bookings.start_time <= '2014-10-24 03:00:00.000000') AND bookings.id != 9 AND bookings.is_confirmed = 't') LIMIT 1
There are two other bookings (with this studio_id) and none of them are confirmed. What gives?
Here are all the bookings with :studio_id => 2
[#<Booking id: 1, studio_id: 2, engineer_id: 5, is_confirmed: false, title: "", allDay: false, created_at: "2014-10-23 19:59:01", updated_at: "2014-10-23 19:59:01", start_time: "2014-10-23 19:00:00", end_time: "2014-10-23 21:00:00", user_id: nil, booker: "Client", client_id: 3>,
#<Booking id: 8, studio_id: 2, engineer_id: 1, is_confirmed: false, title: "", allDay: false, created_at: "2014-10-24 03:07:34", updated_at: "2014-10-24 03:07:34", start_time: "2014-10-23 19:00:00", end_time: "2014-10-23 22:00:00", user_id: nil, booker: "Pat Sullivan", client_id: nil>,
#<Booking id: 9, studio_id: 2, engineer_id: 2, is_confirmed: false, title: "", allDay: false, created_at: "2014-10-24 03:26:17", updated_at: "2014-10-24 03:26:17", start_time: "2014-10-23 20:00:00", end_time: "2014-10-24 03:00:00", user_id: nil, booker: "Client", client_id: 4>]
Update I noticed that the studio_id isn't being noticed with the && in the scope line. How can I have the scope register both? Can I do it within the scope line or should I create a method?
I've also tried a simpler
validates :start_time, :end_time,
:overlap => {
:exclude_edges => ["starts_at", "ends_at"],
:scope => "is_confirmed" && "studio_id"
}, on: :update
This does the same thing -- only uses the later "studio_id"
I know, that the names of options are confusing and I'm sorry for that.
I suggest you to implement your named scope called :confirmed and pass it as :query_option parameter.
I think, it should look like this:
class Booking < ActiveRecord::Base
scope :confirmed_scope, -> {confirmed: true}
validates :start_time, :end_time, :overlap => {
:exclude_edges => ["starts_at", "ends_at"],
:scope => "studio_id",
:query_options => {:confirmed_scope => nil}
}, on: :update
end
BTW... be careful if you are using Rails 4.1, there is a change https://github.com/robinbortlik/validates_overlap#rails-41-update
Short explanation: what you pass as a :scope option, this behave like attribute. But you can extend it by :query_options. What is inside query options will be called in the query chain. So internally it will be called like this:
Booking.confirmed_scope.where("starts_at > 18-02-2014 AND ends_at < 20-02-2014 AND studio_id = 1")
Is it more clear now?

Rename image using associated model - Paperclip

Code
In my image model:
has_attached_file :pic
before_post_process :rename_pic
before_save ->{ p 'before_save ----------------' }
after_post_process ->{ p 'after_post_process --------------' }
def rename_pic
p 'en imagen'
p self
p 'en imagen'
end
In service that has many images:
# don't use accepts_nested_attributes_for
before_save :create_images
attr_accessor :images_attributes
def create_images
# images_attributes example value: { "0"=> {img_attrs}, "1" => {img_attrs1} }
images_attributes.all? do |k, image_attrs|
if image_attrs.delete(:_destroy) == "false"
p 'asd'
image = Image.new image_attrs.merge(service_id: id)
p image.service
p image.service_id
image.save
end
end
end
This is the output I get:
"asd"
"en imagen"
#<Image id: nil, service_id: nil, pic_file_name: "Screen_Shot_2013-04-07_at_5.18.03_PM.png", pic_content_type: "image/png", pic_file_size: 16041, pic_updated_at: "2013-07-30 22:58:46", created_at: nil, updated_at: nil, user_id: nil>
"en imagen"
G"after_post_process --------------"
#<Service id: 427, event_id: nil, min_capacity: nil, max_capacity: nil, price: #<BigDecimal:7fb6e9d73d48,'0.0',9(18)>, image_path: nil, name: "Super Franks", desc: "zxc", created_at: "2013-05-12 19:01:54", updated_at: "2013-07-30 19:32:48", address: "pasadena", longitude: 77.225, latitude: 28.6353, gmaps: true, city: "san francisco", state: "california", country_id: "472", tags: "Banquet", created_by: 22, avg_rating: #<BigDecimal:7fb6efdbcf10,'0.0',9(18)>, views: 27, zip_code: "", address2: "", price_unit: "", category_id: 3, featured: true, publish: true, slug: "banquet-super-franks", discount: nil, currency_code: "USD", video_url: "http://www.youtube.com/watch?v=A3pIrBZQJvE", short_description: "">
427
"before_save ----------------"
Problem
When calling
image = Image.new image_attrs.merge(service_id: id)
Paperclips seems to start processing, and then set service_id.
So when I try to use service inside rename_pic service is nil.
Any ideas on how to handle this?
This solved my problem, I changed:
before_post_process :rename_pic
to:
before_create :rename_pic
and this is rename_pic, for the record:
def rename_pic
extension = File.extname(pic_file_name).downcase
self.pic.instance_write :file_name,
"#{service.hyphenated_for_seo}#{extension}"
end
where service has_many images, and image belongs_to service.
Be carefull with the fix of #juanpastas, because if you change before_post_process to before_create, it will only run when you create your image, and not when you update it. To have the callback still run on update, do this:
class YourImage
has_attached_file :pic
# use both callbacks
before_create :rename_pic
before_post_process :rename_pic
def rename_pic
# assotiated_object is the association used to get pic_file_name
return if self.assotiated_object.nil?
extension = File.extname(pic_file_name).downcase
self.pic.instance_write :file_name,
"#{service.hyphenated_for_seo}#{extension}"
end
end

Why is my after_save callback stopping my ActiveRecord association from saving properly?

When I comment out my after_save call back, my ActiveRecord associations work just fine. In Rails Console, you'd see:
> #report = Report.create :name => "foo"
=> #<Report id: 9, name: "foo", created_at: "2013-03-05 09:51:55", updated_at: "2013-03-05 09:51:55">
> #question = #report.questions.create :description => "bar"
=> #<Question id: 18, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 09:52:32", updated_at: "2013-03-05 09:52:32", additive: false, instructions: nil>
> #report.questions
=> [#<Question id: 18, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 09:52:32", updated_at: "2013-03-05 09:52:32", additive: false, instructions: nil>]
> #question.reports
=> [#<Report id: 9, name: "foo", created_at: "2013-03-05 09:51:55", updated_at: "2013-03-05 09:51:55">]
However, the associations stop working when I add the following after_save callback to question.rb:
def create_matching_surveys
self.reports.each do |report|
report.reviews.each do |review|
review.competitors.each do |competitor|
competitor.surveys.find_or_create_by_question_id(self.id)
end
end
end
end
Then, in Rails Console, you get:
> #report = Report.create :name => "foo"
=> #<Report id: 13, name: "foo", created_at: "2013-03-05 10:20:51", updated_at: "2013-03-05 10:20:51">
> #question = #report.questions.create :description => "bar"
=> #<Question id: 24, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 10:21:02", updated_at: "2013-03-05 10:21:02", additive: false, instructions: nil>
> #report.questions
=> [#<Question id: 24, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 10:21:02", updated_at: "2013-03-05 10:21:02", additive: false, instructions: nil>]
> #question.reports
=> []
This happens whether or not the report has reviews that have competitors.
The strange thing is I thought the callback was meant to happen after the question was saved? So by rights the association should save too before any of this happens, right?
How do I fix it?
UPDATE
I think I have to call the callback in the right spot in the object's life cycle, but I can't find that spot. Here's why I think this:
> #report = Report.create :name => "foo"
=> #<Report id: 20, name: "foo", created_at: "2013-03-05 12:29:35", updated_at: "2013-03-05 12:29:35">
> #question = #report.questions.create :description => "bar"
=> #<Question id: 31, standard_id: nil, description: "bar", element_id: nil, condition_id: nil, blueprint_name: nil, blueprint_url: nil, created_at: "2013-03-05 12:30:14", updated_at: "2013-03-05 12:30:14", additive: false, instructions: nil>
> #question.reports
=> []
> #question.update_attributes :description => "foo"
=> true
> #question.reports
=> [#<Report id: 20, name: "foo", created_at: "2013-03-05 12:29:35", updated_at: "2013-03-05 12:29:35">]
BTW, the method is now in question_observer.rb:
class QuestionObserver < ActiveRecord::Observer
def after_save(model)
model.reload
model.reports.reload
model.reports.each do |report|
report.reviews.each do |review|
review.competitors.each do |competitor|
competitor.surveys.find_or_create_by_question_id(model.id)
end
end
end
return true
end
end
The answer was to use a neat new callback hook called after_commit which was introduced with Rails 3.
See http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#method-i-after_commit.
The only issue is after_commit doesn't work "out of the box" with transactional fixtures, but there are plenty of solutions out there, and I found this one worked well for me: https://supportbee.com/devblog/2012/01/14/testing-after_commitafter_transaction-with-rspec/

Why is Rail2's to_json's include option returning empty hashes?

I'm trying to use Rails(2)'s to_json model serializing mechanism to emit some data from associated models. As my guide I'm referencing the following essentially identical documentation URLs:
http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
http://apidock.com/rails/ActiveRecord/Serialization/to_json
Here is some of the relevant model code:
class WorkEffortAssignment < ActiveRecord::Base
belongs_to :work_effort
belongs_to :assigned_to, :polymorphic => true
belongs_to :assigned_by, :polymorphic => true
Here is the controller code, I'm just trying to dump some JSON for initial testing purposes:
def dump_work_effort_assignments
WorkEffort.include_root_in_json = false
all_assignments = WorkEffortAssignment.all
options = {:include => [:work_effort, :assigned_to, :assigned_by], :only => [:work_effort_id, :assigned_to_id, :assigned_by_id]}
ext_json = "{data:#{WorkEffortAssignment.all.to_json(options)}}"
render :inline => ext_json
end
Here's the first record of Json data with empty hashes for work_effort, assigned_to and assigned_by:
{data:[{"assigned_to":{},"work_effort_id":"9","assigned_to_id":3,"assigned_by":{},"work_effort":{},"assigned_by_id":3}, //etcetera
But below is my console session showing the associations that I'd like to represent in my Json. So what am I doing wrong in my controller when trying to specify the include option for to_json, such that I can easily send associated model data back to the browser. Thanks in advance
>> assignment = WorkEffortAssignment.first
=> #<WorkEffortAssignment id: 1, assigned_at: nil, assigned_from: nil, assigned_
thru: nil, unassigned_at: nil, assigned_to_id: 3, assigned_to_type: "Party", ass
igned_by_id: 3, assigned_by_type: "Party", created_at: nil, updated_at: nil, wor
k_effort_id: "9">
>> assignment.work_effort
=> #<WorkEffort id: 9, description: "Software Architecture Document", type: nil,
started_at: nil, finished_at: nil, projected_completion_time: nil, actual_compl
etion_time: nil, created_at: "2011-08-18 13:39:30", updated_at: "2011-08-25 13:3
9:30", facility_id: nil, facility_type: nil, work_effort_record_id: nil, work_ef
fort_record_type: nil, projected_cost_id: nil, actual_cost_id: nil, parent_id: n
il, lft: 1, rgt: 2>
>> assignment.assigned_to
=> #<Party id: 3, description: "George Jempty", business_party_id: 2, business_p
arty_type: "Individual", list_view_image_id: nil, enterprise_identifier: nil, cr
eated_at: "2011-08-29 14:21:41", updated_at: "2011-08-29 14:21:41">
>> assignment.assigned_by
=> #<Party id: 3, description: "George Jempty", business_party_id: 2, business_p
arty_type: "Individual", list_view_image_id: nil, enterprise_identifier: nil, cr
eated_at: "2011-08-29 14:21:41", updated_at: "2011-08-29 14:21:41">
The :only specifier you have there is throwing it off. The following will include the child-objects:
def dump_work_effort_assignments
WorkEffort.include_root_in_json = false
all_assignments = WorkEffortAssignment.all
options = {:include => [:work_effort, :assigned_to, :assigned_by]}
ext_json = "{data:#{WorkEffortAssignment.all.to_json(options)}}"
render :inline => ext_json
end
...but will not filter out the attributes of the parent. If you want to do that, it may be simpler to just build a hash and convert it to json:
def dump_work_effort_assignments
data = WorkEffortAssignment.all.map {|wea| {
:work_effort => wea.work_effort,
:assigned_to => wea.assigned_to,
:assigned_by => wea.assigned_by
}}
ext_json = "{data:#{data.to_json}}"
render :inline => ext_json
end

Resources