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.
source to share
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
source to share
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.
source to share