Boolean database + Site Design/Architecture Question

Hi all,

As usual, you’ve all been very helpful in the past and I thank you for
continuing to help myself and others.

Today I have the following code/database architecture question which I
would like you guys to help me out with.

Children Table (user has_many children)


is_lost boolean, default => false
is_found boolean, default => false

Basically, a child is “safe” if he is not flagged as lost or found (so a
child has 3 possible states: safe, lost, found).

I created a custom validation to avoid a child from being both lost AND
found with this:

def validate
if is_lost? and is_found?
      errors.add_to_base("A child can't be lost <i>AND</i> found at the
same time!")
    end
end

This all works great and validates correctly. HOWEVER, the only way I’ve
been able to select if a child is lost or found is through check boxes
(not the best way to do this). I think a drop down box would be the
better solution to select if a child is either lost or found (or safe)

My question is this:

Is this the best way to achieve what I am trying to do? Is it better to
just have a column in my table named “status” where the values would be
either Safe, Lost, Found and remove the is_lost and is_found boolean
columns instead?

If the current way I designed it is good, how can I create a drop down
box that could handle both the is_lost and is_found data?

I know this is a disaster (in my eyes) but I would like the opinions and
suggestions of you experienced folks.

Thank you!!!
-Tony

P.S. Let me know if you need more info or code to help you understand
this.

I would go with a single ‘status’ field.

Roy P. wrote:

I would go with a single ‘status’ field.

Thanks Roy, I appreciate the reply. I guess I could just do a…

if @child.status == 'lost'
  ...

I did like the elegance of boolean for things like:

if @child.is_lost?
  ...

Any other suggestions or opinions?

Well, you can still have those is_whatever? methods–just write them on
the model your own self:

class Child
def is_lost?
self.status == ‘lost’
end
def is_found?
self.status == ‘found’
end
def is_safe?
self.status = ‘safe’
end
end

You could even write your own setters (def is_safe=(new_val)), tho that
may be taking it too far…

Roy P. wrote:

Well, you can still have those is_whatever? methods–just write them on
the model your own self:

Touché. Can’t believe I didn’t think about that! Thank you!!

First and foremost, shouldn’t your model be called Child? A single child
shouldn’t be called “a children”, but that’s how you have it set up.
Having
it as is won’t destroy the universe, but it will end up getting really
confusing sooner or later. Rails knows that “children” is the plural
form
of “child”. Open the rails console and type “child”.pluralize to see for
yourself.

That aside, since Children is a stateful object, you should consider the
Acts
As State Machine plugin. All you need is the plugin and a “state”
(varchar)
column in the model’s database table. This gives you all kinds of neat
functionality.

In your rails app root, do this little number:
script/plugin install
http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk/

We will configure your model to have three states: “safe”, “lost”,
and “found”. In your Children model, add the following:

#set the default state
acts_as_state_machine, :initial => :safe

declare three states: “safe”, “lost”, and “found”

state :safe
state :lost
state :found

configure state methods

event :lost do
transitions :from => :safe, :to => :lost
transitions :from => :found, :to => :lost
end
event :found do
transitions :from => :safe, :to => :found
transitions :from => :lost, :to => :found
end
event :safe do
transitions :from => :lost, :to => :safe
transitions :from => :found, :to => :safe
end

That’s it. Now the following methods will work to check the state:
@child.safe? # true/false
@child.lost? # true/false
@child.found? # true/false

The “events” that you declared are used to change the object’s state,
using
event!. For example, to change a safe child to lost, you would call:
@child.lost!
Normally, you would want to name your “events” some kind of verb. This
makes
the method easier to read and understand, but your situation is
peculiar. I
guess you should leave them as is to avoid any further confusion.

You can tell AASM to call additional methods when transitioning between
states, if you wish. You can also block transitions unless certain
conditions
are met. Read up on the callbacks and guard functions here:
http://rails.aizatto.com/2007/05/24/ruby-on-rails-finite-state-machine-plugin-acts_as_state_machine/
(site is currently showing a blank page, but it’s there, I swear!)

I hate to bump and annoy, but can anyone offer a suggestion on my last
reply?

Thanks!

-Tony

Patrick Sullivan wrote:

As State Machine plugin. All you need is the plugin and a “state”
(varchar)
column in the model’s database table. This gives you all kinds of neat
functionality.

Thank you immensely for bring that plugin to my attention. As for the
child naming, I’m using pseudo code for the most part. Sorry about that.

I just have a (silly) question about the event! calls… where do I put
them? :slight_smile: I tried searching for it but didn’t find an exact answer.

I was able to make the state column into a drop box with the three
states like so (in a hurry):

<%= select :child, :state, { "Safe" => "safe", "Lost" => "lost", "Found" 
=> "found"}  %>

So it updates and displays the state just fine (@child.state). HOWEVER,
I’m sure the use of @child.lost!, @child.found!, and @child.safe! is
there for a reason. What is the best way to go about implementing this?

From just some quick brainstorming I’m thinking the below if block in
the update (and create) method would work, but perhaps there is a better
way?

def update
    @child = Children.find(params[:id])

    if params[:state]=='lost'
      @child.lost!
    elsif params[:state]=='found'
      @child.found!
    else
      @child.safe!
    end

...
end

Also, is it possible to use more than one act_as_state_machine per
model? I’m assuming you can by assigning a :column => ‘column_name’. But
confirmation would be nice.

Thank you so much again!!!
-Tony