barnacle.m
barnacle.m

Reputation: 2200

Rails scope with method value comparison

So i'm fairly new to rails and ActiveRecord and I have a need for a scope to filter between Client entities. Basically the scope should return all Client records where the client's current state is equal to a certain state object.

This is calculated by getting a client's last state_change and then pulling that state_change's from_state which is a State object.

I have defined a method to return the current_state however in rails console when I test it with Client.current_state(Client.last) I get this error:

NameError: undefined local variable or method 'state_changes for #<Class:0x0000000685eb88> but when running Client.last.state_changes in console it works fine.

My client.rb

class Client < ActiveRecord::Base
  has_and_belongs_to_many               :users
  belongs_to                            :industry
  belongs_to                            :account
  has_many                              :contacts
  has_many                              :state_changes
  belongs_to                            :head,          class_name: "Client"
  has_many                              :branches,      class_name: "Client", foreign_key: "head_id"
  has_many                              :meetings,      through: :contacts
  has_many                              :sales,         through: :meetings

  scope :prospects, -> (client) { where(Client.current_state(client): State.PROSPECT_STATE) }

  def self.has_at_least_one_sale? (client)
    return client.sales.empty?
  end

  def self.has_account_number? (client)
    return client.account_number.present?
  end

  def self.current_state (client)
    state_changes.last.to_state
  end
end

state_change.rb

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

state.rb

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

  def self.PROSPECT_STATE
    return State.find_by name: 'Prospect'
  end

  def self.CLIENT_STATE
    return State.find_by name: 'Client'
  end

  def self.SUSPECT_STATE
    return State.find_by name: 'Suspect'
  end
end

I also get syntax errors regarding the scope I defined in client.rb. I have followed the ActiveRecord guide but they don't explain how to have chained methods in the actualy scope query.

Upvotes: 3

Views: 7010

Answers (1)

IngoAlbers
IngoAlbers

Reputation: 5802

The reason you get the error NameError: undefined local variable or method 'state_changes for #<Class:0x0000000685eb88> is because you define current_state as a class method and pass the client as a parameter. That's why state_changes is called on the class and not the instance. In this case you would need to use the client to get the state_changes.

def self.current_state (client)
  client.state_changes.last.to_state
end

Also scopes are meant to just chain query logic. I'm not sure if it is possible to just use queries to get your wanted result. And I hope I understood your logic correctly. Alternatively you could use a class method.

def self.prospects (client)
  Client.all.select { |c| c.current_state(c) == State.PROSPECT_STATE }
end

As pointed out by Зелёный in the comment, maybe you also want to just change the methods to instance methods, in which case reading the resource he linked would be very helpful.

Update based on comment:

I think what you actually want is using an instance method for current_state like this:

def current_state
  state_changes.last.to_state
end

And then you can get prospects like this:

def self.prospects
  Client.all.select { |c| c.current_state == State.PROSPECT_STATE }
end

Upvotes: 5

Related Questions