I'm having an issue with my scheduled text messages. I run a rake task that checks to see if a text message should be put into a Sidekiq queue. The record is processed (the text is sent) but a new empty record is generated and the sentstatus is not updated to "true".
send_scheduled_text.rake
require_relative '../../app/workers/send_text_worker'
namespace :send_scheduled_text do
task:texts => :environment do
TextMessage.all.each do |text_message|
if text_message.sentstatus == false
if (Date.today == text_message.scheduled_date) && (Time.now.hour >= text_message.scheduled_time.hour)
# Sidekiq code:
SendTextWorker.perform_async(text_message.id)
end
end
end
end
end
send_text_worker.rb
class SendTextWorker
include Sidekiq::Worker
def perform(text_message_id)
text = TextMessage.find(text_message_id)
text.send_text_message(text.content, text.phone)
end
end
text_message.rb
require 'twilio-ruby'
require 'date'
class TextMessage < ActiveRecord::Base
belongs_to :client, dependent: :destroy
belongs_to :step, dependent: :destroy
has_many :coach_emails
before_save :grab_phone
def grab_phone
self.phone = phone
end
def send_text_message(message, phone)
twilio_sid = ENV["TWILIO_ACCT_SID"]
twilio_token = ENV["TWILIO_AUTH_TOKEN"]
twilio_phone_number = ENV["TWILIO_PHONE_NUMBER"]
begin
#twilio_client = Twilio::REST::Client.new(twilio_sid, twilio_token)
#twilio_client.account.sms.messages.create(
:from => "+1#{twilio_phone_number}",
:to => phone,
:body => message)
rescue Twilio::REST::RequestError => e
puts e.message
end
if e != "400" || e != "500"
self.sentstatus = true
end
self.save!
send
send
Rails console: before rake task is called
(sentstatus is false)
irb(main):001:0> TextMessage.all
TextMessage Load (0.5ms) SELECT "text_messages".* FROM "text_messages"
=> #<ActiveRecord::Relation [#<TextMessage id: 164, client_id: nil, content: "Testing Sidekiq processing", incoming_message: false, created_at: "2015-02-02 04:43:29", updated_at: "2015-02-02 04:43:29", scheduled_date: "2015-02-01", sentstatus: false, step_id: 4, phone: "+14127364161", scheduled_time: "2000-01-01 14:00:00">]>
Rails console: After rake task is called
(sentstatus is false, should be true. I also have this new bizarre empty record)
irb(main):001:0> TextMessage.all
TextMessage Load (0.5ms) SELECT "text_messages".* FROM "text_messages"
=> #<ActiveRecord::Relation [#<TextMessage id: 164, client_id: nil, content: "Testing Sidekiq processing", incoming_message: false, created_at: "2015-02-02 04:43:29", updated_at: "2015-02-02 04:43:29", scheduled_date: "2015-02-01", sentstatus: false, step_id: 4, phone: "+14127364161", scheduled_time: "2000-01-01 14:00:00">,
#<TextMessage id: 165, client_id: nil, content: nil, incoming_message: nil, created_at: "2015-02-02 04:45:24", updated_at: "2015-02-02 04:45:24", scheduled_date: nil, sentstatus: true, step_id: nil, phone: nil, scheduled_time: nil>]>
I have a feeling this is a Sidekiq nuance that I'm missing. Thanks for any thoughts!
I ended up moving my model logic to my worker. Presto -- timing works and I'm not generating any extra nil records.
Related
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.
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
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.
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
The problem I am having with this is Product is trying to create variants before the product is even created and there are certain callbacks for variants that require the product to exist. So how can I rewrite this so that v.save doesn't execute till the object is created or whatever.
Product.class_eval do
validates_presence_of [:style_no, :market_price, :designer, :short_description, :description]
validates_numericality_of [:size_47_quantity,
:size_46_quantity,
:size_45_quantity,
:size_44_quantity,
:size_43_quantity,
:size_42_quantity,
:size_41_quantity,
:size_40_quantity,
:size_39_quantity]
for i in 39..47
define_method:"size_#{i}_quantity" do
if v = self.variants.find_by_size(i)
v.count_on_hand
else
0
end
end
define_method:"size_#{i}_quantity=" do |amount|
# if only there is some method that can postpone all the following if this product hasn't been created yet!
self.id = Product.last.id + 1 unless self.id
v = self.variants.find_by_size(i) || self.variants.new(:size => i)
v.count_on_hand = amount
v.save
end
end
end
You can try this solution:
Product class
class Product < ActiveRecord::Base
validates_presence_of [:style_no, :market_price, :designer, :short_description, :description]
has_many :variants
# This method would check if variant was created or loaded.
#
# So many sequantial calls to it will return same object
def variant_with_size(size)
self.variants.select{|v| v.size == size}.first || self.variants.where('size = ?', size).first
end
module ClassExtensions
def self.included(base)
(39..47).each do |i|
method = "size_#{i}_quantity".to_sym
included_module = Module.new
included_module.module_eval <<EOF
def #{method}
if v = self.variant_with_size(#{i})
v.count_on_hand
else
0
end
end
def #{method}=(amount)
v = self.variant_with_size(#{i}) || self.variants.build(:size => #{i})
v.count_on_hand = amount
v
end
EOF
base.send :include, included_module
end
end
end
include ClassExtensions
end
Variant class
class Variant < ActiveRecord::Base
belongs_to :product
validates :count_on_hand, :numericality => true
end
Usage
Usage example with correct variant amount:
ruby-1.9.2-p180 :001 > p = Product.new
=> #<Product id: nil, style_no: nil, market_price: nil, designer: nil, short_description: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :002 > p.size_39_quantity
=> 0
ruby-1.9.2-p180 :003 > p.size_39_quantity = 2
=> 2
ruby-1.9.2-p180 :004 > p.variants
=> [#<Variant id: nil, product_id: nil, size: 39, count_on_hand: 2, created_at: nil, updated_at: nil>]
ruby-1.9.2-p180 :005 > p.save
=> true
ruby-1.9.2-p180 :006 > p.variants
=> [#<Variant id: 3, product_id: 3, size: 39, count_on_hand: 2, created_at: "2011-04-06 06:34:46", updated_at: "2011-04-06 06:34:46">]
Usage with incorrect variant amount:
ruby-1.9.2-p180 :007 > p1 = Product.new
=> #<Product id: nil, style_no: nil, market_price: nil, designer: nil, short_description: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :008 > p1.size_39_quantity = 'A'
=> "A"
ruby-1.9.2-p180 :009 > p1.save
=> false
ruby-1.9.2-p180 :010 > p1.errors
=> {:variants=>["is invalid"]}
ruby-1.9.2-p180 :011 > p1.variants[0].errors
=> {:count_on_hand=>["is not a number"]}
At a glance, I'd consider using an after_save callback on Product to create product variants.
Something like:
class Product < ActiveRecord::Base
has_many :variants
after_save :create_variants! if :not_a_variant?
OPTIONS = [:size_1_qty, :size_2_qty] # TODO: move to a OptionType model associated with Product
def not_a_variant?
size.nil? # or however you might distinguish a Product from a Variant
end
private
def create_variants!
# OPTIONS could instead be related option_types. perhaps a 'size' option type with values of 40, 41, 42, etc.
OPTIONS.each do |size|
variants.build(...)
end
save!
end
end
I was just reviewing the Spree shopping cart project by Rails Dog and they handle product variants in a similar fashion. You might check it out.