Defining custom methods in a model / class in Rails 4

I have the following model:

class ActivityLog < ActiveRecord::Base
  validates :user_id, :instance_id, :action, presence: true
  validates :user_id, :instance_id, :action, numericality: true

  def log
    ActivityLog.create(
      user_id: current_user ? current_user.id : -1,
      instance_id: instance_id,
      action: actions.index(action)
      )
  end

  private 

  def actions
    ['start','stop','create','destroy']
  end

end

      

When I call the following line from the rails console, I get an error:

ActivityLog.log(user_id: 1, instance_id:1, action: 'create')

# Error returned from console
NoMethodError: undefined method `log' for #<Class:0x007fb4755a26a8>

      

Why isn't my method working? I have defined it in the class, but it says it is undefined. What am I missing or misunderstanding? Thank.

+3


source to share


3 answers


Method of creation log

Say you have a class User

and inside the class you define a method has_cell_phone

. (The content of this method is irrelevant.) When you define a method in a class as def has_cell_phone

, that method can be called on any object User

. While class User

itself is a class object, you must call it the object of which it is the closest class User

. In correct terms, you would write an instance method for an instance of the class User

.

You are getting this error because the method log

you defined only works for the _instance of the class ActivityLog

. If you follow these steps, you can call correctly log

given the current code:

activity_log = ActivityLog.create  # with required params
activity_log.log

      

Second, you call log

with parameters, while your method definition does not. (It will look like def log(params)

.)

Now, this is where you modify your existing code. When you want to call a method on an entire class (which means the class itself), you add a keyword self

to the class definition. For example, for a class, User

it would be def self.create_user_with_cell_phone

. You can add arguments to this method as well. The arguments that you provide in your "call method" line, I would add them to your class method, for example:

def self.log(instance_id, action)
  # ...
end

ActivityLog.log(1, 'create')

      



You don't need to include user_id

, because based on your logic, it checks if the object is current_user

true

and follows from there.

Creating a class constant

Second look at your question, I found you are defining a method actions

. Remember what I said about instance methods? Since it turns out that actions

it will always remain constant, I recommend that you make it one! For this, it is recommended that you place the following line in your class before the method definitions.

ACTIONS = ['start','stop','create','destroy']

      

Then, at any time when you want to call actions

, while in class ActivityLog

, you do the following: ACTIONS.index(action)

. If you want to call this constant is the class, you should do this: ActivityLog::ACTION

. This is similar syntax for calling a method on a class, you use instead ::

to separate the class from the constant. Re-examining your code, it should look like this:

class ActivityLog < ActiveRecord::Base
  ACTIONS = ['start','stop','create','destroy']

  validates :user_id, :instance_id, :action, presence: true
  validates :user_id, :instance_id, :action, numericality: true

  def self.log(instance_id, action)
    ActivityLog.create(
      user_id: (current_user ? current_user.id : -1),
      instance_id: instance_id,
      action: ACTIONS.index(action)
    )
  end
end

      

+9


source


log

is an instance method as defined; it will only work if you have a specific instance ActivityLog

.

If you want to make this method of a class, you must attach it to the class using a keyword self

.



def self.log
    # code here
end

      

+2


source


You write an instance method and call it like a class method.

To write a class method, you need to add self

( self.log

, self.actions

) before the method names . This will allow you to call the method as you expect, and is probably the best way to write an alternate constructor like this. If your methods do not depend on the concrete instance of the class that you seem to be doing here, it is better to make them class methods.

Alternatively, you can instantiate your class and call the instance methods that you have defined. Instead, ActivityLog.log

create a new log with Activity.new

and call the log method on it. In one line it will look like Activity.new.log

, but you should probably store the new object in a variable to keep track of it.

The last alternative might be to use the method initialize

. Once written def initialize

, you change the constructor for your class so that instead of calling, ActivityLog.log

you can call ActivityLog.new

. This makes it clearer that you are building a new object and are idiomatic in ruby. However, it removes the descriptive method name. I would recommend this route if you are not going to create your class with any other methods, but if you want to have multiple, go to the class method.

0


source







All Articles