How can I cause the state_machine block to use the default accessor instead of my custom accessor? - ruby

I'm implementing pluginaweek's state_machine gem. I narrowed the code down to just this so I can understand the problem more easily. Let's assume I only have one status for now:
class Event < ActiveRecord::Base
state_machine :status, :initial => :active_offer do
end
def status
'Active Offer'
end
end
The error I'm getting when creating new Event objects either via seeding or via the browser is {:status=>["is invalid"]}.
The plan is to include a condition for all the different statuses and return a custom string to the view. The entire project's views currently use the .status syntax so I'm trying to smoothly install this. I started this solution based on reading the api docs:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Overwriting+default+accessors
This is my main goal:
def status
if read_attribute(:status) == 'active_offer' && self.start_date > Time.now
'Active Offer'
elsif read_attribute(:status) == 'active_offer' && self.start_date < Time.now
'Expired'
else read_attribute(:status) == 'cancelled'
'Cancelled'
end
end
What can I do to make the state_machine block use the normal accessor so it gets the database value?
THE SILVER BULLET SOLUTION:
My main problem was the accessor override status. When state_machine code was running, it was reading the current status through my accessor override and therefore getting a custom string returned, which was an invalid status. I had to make state_machine use :state. I didn't do this originally because I already had a :state attr for provinces and such, so I migrated that to :address_state.

Pretty sure you just need to change the definition's name:
class Event < ActiveRecord::Base
state_machine :status, :initial => :active_offer do
end
def active_offer
'Active Offer'
end
end
EDIT:
If I understand you correctly, which I'm not certain I do, this will work:
state_machine :status, initial: :active_offer do
event :expire do
transition all => :expired
end
event :cancel do
transition all => :cancelled
end
end
Then you can do your if... statements, etc., in the controller or something, and transition them with something like #event.expire or if #event.expired. If you want it automated you'll need something like whenever.

Related

state_machine using after_failure with ActiveRecord transactions

I'm using ruby state_machine to manage processing documents. As the document progresses the SM states manage the extra validations needed.
If any step fails, such as the document being the wrong dimensions, I want to record this so I can inform a user who is waiting for the conversion to complete.
My plan was to use SM after_failure to fill in a failed_at timestamp in the model. The polling user interface would see the processing has failed and display an appropriate message.
My issue is that if a transition fails it rolls back the transaction including the after_failure method. This means the user never finds out that the processing failed and unfortunately sits in front of their screen indefinitely.
In the example below I want the :fail_transaction method to run despite the transition failing. I'm open to any ideas or alternate ways to notify the user of failure.
Notes:
The :use_transaction => false option for state machine doesn't seem
to make a difference.
I considered putting the fail conversion into a worker queue to escape the transaction but as I'm using delayed_job so queue entries are also wound back.
This project is shamefully still on Rails 2.3.x & ruby 1.8.x
Example code:
state_machine :state, :initial => :new do
state :new
state :original_uploaded do
validates_presence_of :original_file
end
state :original_converted do
validates_presence_of :pdf
end
state :conversion_complete do
validates :pdf_dimensions_correct
end
event :process do
transition :new => :original_uploaded
transition :original_uploaded => :original_converted
transition :original_converted => :conversion_complete
end
after_transition :on => :process, :do => :process
before_transition any => :original_converted, :do => :convert_original_to_pdf
before_transition any => :conversion_complete, :do => :extract_pdf_metadata
after_failure :on => :process, :do => :fail_conversion
end
def fail_conversion
update_attribute(:failed_at, Time.now) # Should update dispite being invalid state
end
def convert_original_to_pdf
#...
end
def extract_pdf_metadata
#...
end
def pdf_dimensions_correct
errors.add(...)
end
Thanks in advance!

Defining method_missing on Active record in rails 4 throws SystemStackError: stack level too deep on attributes

I recently upgraded my app to rails 4.0 and ruby 2.0
I'm having problems understanding why my method_missing definitions won't work. I'm pretty sure I'm not doing anything differently than I was before.
Specifically, I'm trying to create a method that lets an ActiveRecord object respond to a call to an object it belongs_to via a polymorphic relationship.
Here are my classes:
song.rb
class Song < ActiveRecord::Base
has_many :events, :as => :eventable
end
event.rb
class Event < ActiveRecord::Base
belongs_to :eventable, :polymorphic => true
def method_missing(meth, *args, &block)
if meth.to_s == self.eventable_type
self.eventable
else
super
end
end
end
I want to be able to call event.song when the eventable_type of event == 'Song'
The issue is on the self.eventable_type, which triggers a stack overflow.
What am I missing here?
It seems as though the eventable_type method isn't yet defined when method_missing triggers (some methods in Rails get dynamically defined through method_missing the first time you call them).
I'd try a different means of getting the value you want; perhaps self.attributes["eventable_type"] will work?

Why is the stubbed method not called?

It seems I understood something wrong. I have a class
module Spree
class OmnikassaPaymentResponse
#...
# Finds a payment with provided parameters trough ActiveRecord.
def payment(state = :processing)
Spree::Payment.find(:first, :conditions => { :amount => #amount, :order_id => #order_id, :state => state } ) || raise(ActiveRecord::RecordNotFound)
end
end
end
Which is specced in Rspec:
describe "#payment" do
it 'should try to find a Spree::Payment' do
Spree::Payment.any_instance.stub(:find).and_return(Spree::Payment.new)
Spree::Payment.any_instance.should_receive(:find)
Spree::OmnikassaPaymentResponse.new(#seal, #data).payment
end
end
This, however, always throws ActiveRecord::RecordNotFound. I expected any_instance.stub(:find).and_return() to make sure that whenever, wherever I call a #find on whatever instance I happen to have of Spree::Payment, it returns something.
In other words: I would expect the stub.and_return would avoid getting to || raise(ActiveRecord::RecordNotFound). But it does not.
Is my assumption wrong, my code? Something else?
In your case find is not an instance method, but a class method of Spree::Payment. That means you should stub it directly without any_instance like that:
Spree::Payment.stub(:find).and_return(Spree::Payment.new)

Changing associated objects don't get save with rails model object?

having this code block of an example rails model class:
class Block < ActiveRecord::Base
has_many :bricks, :autosave => true
def crunch
bricks.each do |brick|
if brick.some_condition?
brick.name = 'New data'
brick.save # why do I have to call this?
end
end
end
end
class Brick < ActiveRecord::Base
belongs_to :block, :autosave => true
end
I found that the only way to make sure the changes within the associated objects get saved for me, was to call brick.save manually. Even thought I use :autosave => true
Why?
Probably the autosave option has a misleading name. By the way, it's the expected behaviour. The option is meant for association. So if you modify an object in a relation and save the other object then ActiveRecord saves the modified objects. So, in your case, you could change your code to:
def crunch
bricks.each do |brick|
if brick.some_condition?
brick.name = 'New data'
end
end
save # saving the father with autosave should save the children
end
You could use any of the helper methods available: update_attribute, update_attributes, update_column...
More info: Rails: update_attribute vs update_attributes

Rails3: Nested model - child validates_with method results in "NameError - uninitialized constant [parent]::[child]"

Consider the following parent/child relationship where Parent is 1..n with Kids (only the relevant stuff here)...
class Parent < ActiveRecord::Base
# !EDIT! - was missing this require originally -- was the root cause!
require "Kid"
has_many :kids, :dependent => :destroy, :validate => true
accepts_nested_attributes_for :kids
validates_associated :kids
end
class Kid < ActiveRecord::Base
belongs_to :parent
# for simplicity, assume a single field: #item
validates_presence_of :item, :message => "is expected"
end
The validates_presence_of methods on the Kid model works as expected on validation failure, generating a final string of Item is expected per the custom message attribute supplied.
But if try validates_with, instead...
class Kid < ActiveRecord::Base
belongs_to :parent
validates_with TrivialValidator
end
class TrivialValidator
def validate
if record.item != "good"
record.errors[:base] << "Bad item!"
end
end
end
...Rails returns a NameError - uninitialized constant Parent::Kid error following not only an attempt to create (initial persist) user data, but also when even attempting to build the initial form. Relevant bits from the controller:
def new
#parent = Parent.new
#parent.kids.new # NameError, validates_* methods called within
end
def create
#parent = Parent.new(params[:parent])
#parent.save # NameError, validates_* methods called within
end
The error suggests that somewhere during model name (and perhaps field name?) resolution for error message construction, something has run afoul. But why would it happen for some validates_* methods and not others?
Anybody else hit a wall with this? Is there some ceremony needed here that I've left out in order to make this work, particularly regarding model names?
After a few hours away, and returning fresh -- Was missing require "Kid" in Parent class. Will edit.

Resources