Somehow this one is really perplexing me, everything is really there, but somehow it doesn't work:
neither one of the self.status_transition.update do not work
require 'state_machine/core'
class V1::Order < ActiveRecord::Base
extend StateMachine::MacroMethods
has_one :status_transition
after_create :create_status
def create_status
self.create_status_transition if self.status_transition.nil?
end
state_machine initial: :draft do
state :draft, value: 0
state :placed, value: 1
state :paid, value: 2
state :canceled, value: 3
after_transition any => any do |t|
self.status_transition.update(event: t.event,
from: t.from, to: t.to)
#self.status_transition.update(event: "dsa", from: "ds", to: "dsd")
end
event :place do
transition :draft => :placed
end
ERROR:
2.1.1 :004 > o.place
(0.1ms) begin transaction
SQL (0.9ms) UPDATE "v1_orders" SET "state" = ?, "updated_at" = ? WHERE "v1_orders"."id" = 19
[["state", 1], ["updated_at", "2014-11-28 12:48:40.256820"]]
(91.7ms) rollback transaction
NoMethodError: undefined method `status_transition' for #<StateMachine::Machine:0x00000107aa78e8>
o = V1::Order.create # => #<V1::Order id: 22, state: 1, user_id: nil, created_at: "2014-11-28 13:29:03", updated_at: "2014-11-28 13:29:03", vat: #<BigDecimal
:101c09fe8,'0.2E2',9(18)>>
o.status_transitions # IS CREATED
o.status_transition # => #<V1::StatusTransition id: 23, event: nil, from: nil, to: nil, order_id: 28, created_at: "2014-11-28 13:25:55", updated_at: "2014-1
1-28 13:25:55">
o.place returns the above error .
According the documentation block shell be passed as value for :do key to ::after_transition callback, so:
class V1::Order
after_transition any => any do |order, transition|
p order # disable this as needed
order.status_transition.update(event: transition.event, from: transition.from, to: transition.to)
end
end
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.
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.
irb(main):001:0> hotel=Hotel.find(1)
←[1m←[36mHotel Load (1.0ms)←[0m ←[1mSELECT `hotels`.* FROM `hotels` WHERE `hotels`.`hotel_Id` = 1 LIMIT 1←[0m
=> #<Hotel hotel_Id: 1, hotel_Name: "Hotel Swosti", hotel_address: nil, hotel_location: "Bhubaneswar", hotel_contactNo: nil, crea
ted_at: nil, updated_at: nil>
irb(main):002:0> hotel.menus
←[1m←[35mMenu Load (1.0ms)←[0m SELECT `menus`.* FROM `menus` WHERE `menus`.`hotel_id` = 1
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):003:0> first_menu=Menu.new(:menu_item_name=>'Rajma',:price=>30,:item_type=>'Half')
=> #<Menu hotel_Id: nil, menu_item_id: nil, menu_item_name: "Rajma", price: 30, item_type: "Half", created_at: nil, updated_at: n
il>
irb(main):004:0> first_menu.hotel
=> nil
irb(main):005:0> hotel.menus=first_menu
NoMethodError: undefined method `each' for #<Menu:0x512be78>
migration:
create_table :menus,:id=>false do |t|
t.integer 'hotel_Id'
t.primary_key 'menu_item_id'
t.string 'menu_item_name'
t.integer 'price'
t.string 'item_type'
end
add_index("menus","hotel_Id")
end
end
If you want to add first_menu to hotel.menus association, you should do:
hotel.menus << first_menu
The error occurs because Hotel#menus= setter expects collection of Menu objects as parameter.
hotel.menus is a relation. The association that you used returns an array of hotel menus.
To get the first member, a single menu, you could use hotel.menus.first.
If you want to create a new menu for a hotel, you'll probably be better off using:
hotel.menus.build(menu_item_name: 'Rajma', price: 30, item_type: 'Half')
hotel.save!
or the create form - depending on what else you want to do with the hotel or the menu, before you save.
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.
I can't seem to get the state_machine gem (http://github.com/pluginaweek/state_machine/) to work on existing records (it works correctly on new records).
Here's my model:
class Comment < ActiveRecord::Base
state_machine :state, :initial => :pending do
event :publish do
transition all => :published
end
end
end
and here's an IRB session that demonstrates the issue (I did ActiveRecord::Base.logger = Logger.new(STDOUT) to make it easier to read):
>> c = Comment.new
=> #<Comment id: nil, song_id: nil, author: nil, body: nil, created_at: nil, updated_at: nil, state: "pending">
>> c.state
=> "pending"
>> c.publish
Comment Create (0.6ms) INSERT INTO "comments" ("updated_at", "body", "author", "song_id", "created_at", "state") VALUES('2009-11-02 02:44:37', NULL, NULL, NULL, '2009-11-02 02:44:37', 'published')
=> true
>> Comment.last.state
Comment Load (0.4ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> "published"
>> c = Comment.create
Comment Create (0.5ms) INSERT INTO "comments" ("updated_at", "body", "author", "song_id", "created_at", "state") VALUES('2009-11-02 02:44:47', NULL, NULL, NULL, '2009-11-02 02:44:47', 'pending')
=> #<Comment id: 4, song_id: nil, author: nil, body: nil, created_at: "2009-11-02 02:44:47", updated_at: "2009-11-02 02:44:47", state: "pending">
>> c.publish
=> true
>> c.save
=> true
>> Comment.last.state
Comment Load (0.4ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> "pending"
I.e., everything works fine when I publish an unsaved comment, but when I try to publish a comment that's already saved, nothing happens.
Another Edit: Perhaps the root of the problem?
=> true
>> a = Comment.last
Comment Load (1.3ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> #<Comment id: 3, song_id: nil, author: nil, body: nil, created_at: "2009-11-03 03:03:54", updated_at: "2009-11-03 03:03:54", state: "pending">
>> a.state
=> "pending"
>> a.publish
=> true
>> a.state
=> "published"
>> a.state_changed?
=> false
I.e., even though the state has actually changed, state_changed? is returning false and therefore Rails won't update the corresponding database row when I call save.
It works when I turn off partial updates, but not when I try state_will_change!:
>> Comment.partial_updates = false
=> false
>> c = Comment.create
Comment Create (0.5ms) INSERT INTO "comments" ("updated_at", "body", "author", "song_id", "created_at", "state") VALUES('2009-11-07 05:06:49', NULL, NULL, NULL, '2009-11-07 05:06:49', 'pending')
=> #<Comment id: 7, song_id: nil, author: nil, body: nil, created_at: "2009-11-07 05:06:49", updated_at: "2009-11-07 05:06:49", state: "pending">
>> c.publish
Comment Update (0.9ms) UPDATE "comments" SET "created_at" = '2009-11-07 05:06:49', "author" = NULL, "state" = 'published', "body" = NULL, "song_id" = NULL, "updated_at" = '2009-11-07 05:06:53' WHERE "id" = 7
=> true
>> Comment.last.state
Comment Load (0.5ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> "published"
>> Comment.partial_updates = true
=> true
>> c = Comment.create
Comment Create (0.8ms) INSERT INTO "comments" ("updated_at", "body", "author", "song_id", "created_at", "state") VALUES('2009-11-07 05:07:21', NULL, NULL, NULL, '2009-11-07 05:07:21', 'pending')
=> #<Comment id: 8, song_id: nil, author: nil, body: nil, created_at: "2009-11-07 05:07:21", updated_at: "2009-11-07 05:07:21", state: "pending">
>> c.state_will_change!
=> "pending"
>> c.publish
=> true
>> c.save
=> true
>> Comment.last.state
Comment Load (0.5ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> "pending"
EDIT:
More weirdness:
>> a = Comment.last
Comment Load (1.2ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> #<Comment id: 5, song_id: nil, author: nil, body: nil, created_at: "2009-11-02 06:33:19", updated_at: "2009-11-02 06:33:19", state: "pending">
>> a.state
=> "pending"
>> a.publish
=> true
>> a.state
=> "published"
>> a.save
=> true
>> a.id
=> 5
>> Comment.find(5).state
Comment Load (0.3ms) SELECT * FROM "comments" WHERE ("comments"."id" = 5)
=> "pending"
Compare to:
>> a = Comment.last
Comment Load (0.3ms) SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> #<Comment id: 5, song_id: nil, author: nil, body: nil, created_at: "2009-11-02 06:33:19", updated_at: "2009-11-02 06:33:19", state: "pending">
>> a.state = "published"
=> "published"
>> a.save
Comment Update (0.6ms) UPDATE "comments" SET "state" = 'published', "updated_at" = '2009-11-02 08:29:34' WHERE "id" = 5
=> true
>> a.id
=> 5
>> Comment.find(5).state
Comment Load (0.4ms) SELECT * FROM "comments" WHERE ("comments"."id" = 5)
=> "published"
I came over the same issue 3 years after so it worth answering here to save other folks's time.
You need to have column called 'state' in your table, so state_machine will able to make the state persistant.
Just add it to your migration - t.string :state
Can you please retry your state transitions with publish**!** instead of publish
Not contributing anything useful, but I just wanted to say I'm struggling with this error as well, in multiple state_machines throughout my application. And I can't switch to AASM, because I need to have more than one state_machine in the same model... So frustrating!
Anyway, you're not alone, it definitely still needs a solution.
Does this still happen with partial updates turned off? Comment.partial_updates = false
If so, then we know the issue is with identifying dirty objects. You should be able to call c.state_will_change! before you call c.publish
Does the model call super when it's initialized?
The state_machine documentation says it's required for states to get initialized
def initialize
#seatbelt_on = false
super() # NOTE: This *must* be called, otherwise states won't get initialized
end
Again, not a real answer to your question, but here I tried to simulate your session:
>> c = Comment.new
=> #<Comment id: nil, body: nil, created_at: nil, updated_at: nil, state: "pending">
>> c.state
=> "pending"
>> c.publish
=> true
>> Comment.last.state
=> "published"
>> c = Comment.create
=> #<Comment id: 4, body: nil, created_at: "2009-11-05 07:12:53", updated_at: "2009-11-05 07:12:53", state: "pending">
>> c.publish
=> true
>> c.save
=> true
>> Comment.last.state
=> "published"
As you can see, it works as expected for me. Checked it twice.
(I created a model with body and state attributes and put your code in it.)
Try to remove :state from definition:
FROM:
state_machine :state , :initial => :pending do
TO
state_machine :initial => :pending do