barnacle.m barnacle.m - 12 days ago 9
Ruby Question

Applying methods in ActiveRecord query with joined tables

I am trying to retrieve a list of clients who's last

state_change
has a
to_state_id
equal to the value of the
type
argument (in
self.filter_by
).

I have already defined a method called
current_state
which retrieves the current
to_state
of a client, but I am unsure of how to apply this in a query.
I have also tried to use
state_changes.last.to_state_id
in a query but that fails as it thinks 'last' is a table, when it is actually a method.

My relations are as follows. A
client
has many
state_changes
, and a
state_change
has a
from_state
and a
to_state
.

class State < ActiveRecord::Base
has_many :from_states, class_name: 'StateChange', foreign_key: :from_state_id
has_many :to_states, class_name: 'StateChange', foreign_key: :to_state_id
end

class StateChange < ActiveRecord::Base
belongs_to :client
belongs_to :from_state, class_name: "State", foreign_key: :from_state_id
belongs_to :to_state, class_name: "State", foreign_key: :to_state_id
end

class Client < ActiveRecord::Base
has_many :state_changes

def current_state
state_changes.last.to_state
end

def self.filter_by(type)
# querying for clients which have a current_state with id equal to type
joins(:state_changes).where('state_changes.last.to_state_id=?', type)
end
end


I am fairly new to ActiveRecord and ruby so be gentle :)

Answer

This may very well be possible with Rails query, but given the performance hit you'd take and potential to add it to other queries, I recommend you cache the current value somewhere. Here are a couple options...

1) You can add a column to the client and set it whenever the client state changes. You do this after_create on the state_change model.

In the migration

add_column :clients, :current_state_id, :integer

And point to the state record that is the current state.

def self.filter_by(type)
  where(:current_state => type )
end

2) Or, you can add a current boolean to the state_changes table and set it when current state changes. You can put an index on this, and set a has_one on client

has_one :current_state, -> { where(current: true) }, class_name => "StateChange"

This is how you add a partial index to a boolean column:

Adding an index on a boolean field

If you did this, you'd then query like so:

def self.filter_by(type)
  joins(:current_state).where(:state_changes { :to_state_id => type })
end

In your case, I'd pick #1. It feels like current state could be on the client and you'd use that a lot.

Comments