How to Define_Method in Rails Models

define method in model that can be accessed in controller

you have to assign self.method_name to use as a class method

Follow following rule for Model methods

Class Method

def self.problem

end

in controller

Report.problem

Instance method

def problem

end

in controller

report =  Report.new
report.problem

Can I define_method in rails models?

There's nothing magical or about a rails model, it's just a normal class with a bunch of pre-existing methods,

So, the question is "can I define_method in a class"?

Part 1: Yes you can.

The important distinction is than you can define method in a class not in an instance method

For example:

class Cow
define_method "speak" do
"MOOOO"
end
end

Cow.new.speak
=> "MOOOO"

This should work fine. Note you're defining it on the class Cow, so any other Cows that you already have will automatically get that method added.

Part 2: What do you do if you want to define a method from within an instance method?

You can't define methods from an instance method, so you have to grab the class, and use that to define the method. Like this:

class Cow
def add_speak
self.class.send(:define_method, :speak) do
"MOOOO added"
end
end
end

Cow.new.speak
NoMethodError: undefined method 'speak' for #<Cow:0xb7c48530>

Cow.new.add_speak
Cow.new.speak
=> "MOOOO added"

Problem solved. Astute readers will note that in this example I'm using send(:define_method) - this is needed because define_method is private, and private methods are only accessible to the thing they're in. In this case, define_method is in the class, we are in the instance, so we can't directly access it.

As above though, we're adding the method directly to the class, so all other Cows which already exist will automatically also get the speak method added.

Part 3: What do you do if you want to define a method for only 1 object, not all objects of that class?

Example:

class Cow
def add_speak_just_me
class << self
define_method "speak" do
"MOOOO added for just me"
end
end
end
end

Cow.new.speak
NoMethodError: undefined method 'speak' for #<Cow:0xb7c72b78>

c = Cow.new
c.add_speak_just_me
c.speak
=> "MOOOO added for just me" # it works, hooray

Cow.new.speak # this new cow doesn't have the method, it hasn't been automatically added
NoMethodError: undefined method `speak' for #<Cow:0xb7c65b1c>

How does this work? Down the rabbithole you go!

Read this: http://dannytatom.me/metaid/ and good luck. It helps when you realise that 'adding a method' to an instance isn't actually adding it to the instance at all :-)

Rails ActiveRecord: Where to define method?

First of all, where returns an ActiveRecord::Relation object, not an array. It behaves similar to an array, but inherits a load more methods to process data/ construct SQL for querying a database.

If you want to add additional functionality to the ActiveRecord::Relation class, you can do something like this:

class ActiveRecord::Relation 
def your_method
# do something
end
end

This will need to reside somewhere meaningful, such as the lib directory or config/initializers.

This should allow you to do something like

Service.where(id: [1,2,3]).your_method

You can do something similar for any Ruby class, like the Hash, or Array class as well.

However, there's almost ALWAYS a better solution than extending/ overriding Rails/ Ruby source classes...

Defining custom methods within a model/class in Rails 4

Creating method log

Say you have a class User, and within the class, you define the method has_cell_phone. (The contents of that method does not matter.) When you define a method in a class as def has_cell_phone, that method can be called on any User object. While the class User is itself a class object, you would call it on an object whose immediate class is User. In correct terms, you would be writing an instance method for an instance of the User class.

You are getting that error because the method log you defined works only for an _instance of the ActivityLog class. If you do the following, you can call log correctly, given your current code:

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

Secondly, you are calling log with parameters, while your method definition does not require any. (That would look like def log(params).)

Now, here is where you modify your existing code. When you want to call a method on the entire class (meaning the class itself), you prepend the keyword self to the class-method definition. For example, for the User class, it would be def self.create_user_with_cell_phone. You can also add arguments to that method. The arguments you provide in your "method call" line, I would add those to your class method, like so:

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

ActivityLog.log(1, 'create')

You would not need to include the user_id, because, based on your logic, it checks if the current_user object is true, and follows from there.

Creating a class constant

A second look at your question, I found that you are defining a method actions. Remember what I said about instance methods? Since it appears that actions will always remain constant, I recommend that you make it one! To do so, it's recommended you place the following line in your class, before any method definitions.

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

Then, any time you want to call ACTIONS while inside the ActivityLog class, you do the following: ACTIONS.index(action). If you want to call this constant outside of its class, you would do this: ActivityLog::ACTION. It is similar syntax to a class-method call, instead you use :: to separate the class from the constant. Re-examining your code, it should now 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

Define method on activerecord relation

Create a self.summed_values method directly in the transaction model.

Define method with arbitrary name

In my opinion, for your use case it would be cleaner to just refactor it to DRY it up:

private def average(value)
(value.to_f / games_played).round(2)
end

def average_points_for
average(@points_for)
end

def average_points_against
average(@points_against)
end

But, as comments indicate, you could use define_method. Rails does it to great benefit, as does Ruby itself in, say, OpenStruct; but it is an overkill in this case. If you really wanted to do it, this would be the way (untested, may contain bugs):

%i(for against).each do |which|
define_method(:"average_points_#{which}") do
(instance_variable_get(:"@points_#{which}").to_f / games_played).round(2)
end
end

Where to define method to create related model

The best practice is that your controller should follow the Single Responsiblity Principle. If you think of this in REST terms your controller should strive to only be responsible for a single resource.

Your OrdersController should be responsible for CRUD:ing orders, your ShipmentsController shipments etc.

However you also have the concept of nested resources. Orders have many line-items and may have many shipments.

Our routes could look something like this (note that i'm omitting the /v1/api/ prefix for brevity).

Rails.application.routes.draw do
resources :orders, shallow: true, except: [:new, :edit] do
resources :line_items, except: [:new, :edit]
resources :shipments, except: [:new, :edit]
end
end

If we run rake routes you can see how this maps urls to controllers:

          Prefix Verb   URI Pattern                            Controller#Action
order_line_items GET /orders/:order_id/line_items(.:format) line_items#index
POST /orders/:order_id/line_items(.:format) line_items#create
line_item GET /line_items/:id(.:format) line_items#show
PATCH /line_items/:id(.:format) line_items#update
PUT /line_items/:id(.:format) line_items#update
DELETE /line_items/:id(.:format) line_items#destroy
order_shipments GET /orders/:order_id/shipments(.:format) shipments#index
POST /orders/:order_id/shipments(.:format) shipments#create
shipment GET /shipments/:id(.:format) shipments#show
PATCH /shipments/:id(.:format) shipments#update
PUT /shipments/:id(.:format) shipments#update
DELETE /shipments/:id(.:format) shipments#destroy
orders GET /orders(.:format) orders#index
POST /orders(.:format) orders#create
order GET /orders/:id(.:format) orders#show
PATCH /orders/:id(.:format) orders#update
PUT /orders/:id(.:format) orders#update
DELETE /orders/:id(.:format) orders#destroy

One key thing you should note is that all this is done with the standard CRUD verbs. If you start adding wonky stuff like OrdersController#create_shipment you're just doing it wrong.

However in reality sometimes the domain gets complex and its impossible to perfectly adhere to REST and the SRP - thats ok - just try to think if what you are trying to do can be done with just a regular CRUD action first.

How to define method serializer in Rails netflix fast_jsonapi in Rails?

From the readme of fast_jsonapi

class MovieSerializer
include FastJsonapi::ObjectSerializer

attributes :name, :year

attribute :name_with_year do |object|
"#{object.name} (#{object.year})"
end
end

Extending on that you could do something like

class MovieSerializer
include FastJsonapi::ObjectSerializer

attributes :name, :year

attribute :name_with_year do |object|
foo(object)
end

def foo(object)
# do something with object
end
end

Ruby define method to return proc which makes use of an argument

Your example has two problems:

  1. You can't call a "proc full of methods" like that -- it'll work as an association extension, but there the block is evaluated as a module body, not called.

  2. The def keyword resets the local variable scope. To get a value into a function, you can either define it using define_method instead (that block retains surrounding scope), or put the value somewhere else the function will be able to find it (a class variable, for example).


def test(word)
proc do
define_method(:hello) do
puts word
end
end
end

Class.new(&test("hello")).new.hello

Separately, if you're defining approximately the same method on several associations, there might be a simpler path by defining them as class-level scopes.



Related Topics



Leave a reply



Submit