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:
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
call
ed.The
def
keyword resets the local variable scope. To get a value into a function, you can either define it usingdefine_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
How to Do Standard Deviation in Ruby
Library Not Loaded: /Usr/Local/Opt/Readline/Lib/Libreadline.6.Dylib (Loaderror)
How to Use Capybara in Pure Ruby (Without Rails)
Neither Ruby and Nor Irb Can Load .Rb File in Current Directory
Ruby Method with Maximum Number of Parameters
Ruby: How to Pass All Parameters and Blocks Received by One Method to Another
How to Configure an Extra/Different Migrations Folder
Getting a Dns Txt Record in Ruby
Which Ruby Memoize Pattern Does Activesupport::Memoizable Refer To
Rails Flash Message Remains for Two Page Loads
Rails 5.2 Active Storage Add Custom Attributes
Ruby: Get All Keys in a Hash (Including Sub Keys)
How to Use Define_Method Inside Initialize()
Rails: Respond_To JSON and HTML