# app/models/product.rb
class Product < ApplicationRecord
def self.method1(param1)
# Here I want to call method2 with a parameter
method2(param2)
end
def method2(param2)
# Do something
end
end
I call method1 from controller. When I run the program. I got an error:
method_missing(at line method2(param2))
.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.0/lib/active_record/relation/batches.rb:59:in `block (2 levels) in find_each
...
class Product < ApplicationRecord
def self.method1(param1)
# Here I want to call method2 with a parameter
method2(param2)
end
def self.method2(param2)
# Do something
end
end
Explanation: first one is a class method, the latter was an instance method. Class methods don't need a receiver (an object who call them), instance methods need it. So, you can't call an instance method from a class method because you don't know if you have a receiver (an instanciated object who call it).
It does not work because method2 is not defined for Product object.
method2 is an instance method, and can be called only on the instance of Product class.
Of course #Ursus and #Andrey Deineko answers are right solution of this problem. Besides that, if anyone want to know how we can call instance method with in class method(though this is not actually class method in ruby) for those self.new.instance_method.
Related
I am getting a error using action cable,
NameError (undefined local variable or method `connections_info' for MicropostNotificationsChannel:Class):
app/channels/micropost_notifications_channel.rb:12:in `notify'
app/models/notification.rb:8:in `block in <class:Notification>'
app/controllers/likes_controller.rb:11:in `create'
Rendering C:/RailsInstaller/Ruby2.2.0/lib/ruby/gems/2.2.0/gems/actionpack-5.0.0.1/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb
...
To my knowledge and from using controllers and models I can call the class method after_commit -> {MicropostNotificationsChannel.notify(self)} and then get to the self.notifiy(notification) and then as connections_info is a instance method I should be able to call it inside that class and carry out my code but I get the error here?
class Notification < ApplicationRecord
...
after_commit -> {MicropostNotificationsChannel.notify(self)}
end
The action cable micropost notification channel
class MicropostNotificationsChannel < ApplicationCable::Channel
def subscribe
...
end
def unsubscribe
...
end
def self.notifiy(notification)
connection_results = connections_info
puts connection_results.inspect
end
end
Channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base
def connections_info
# do stuff here
end
end
end
You've defined connections_info as an instance method in ApplicationCable::Channel, but notify is a class method and so it's looking for methods at the class level instead of the instance level. Sometimes classes will get around this by using method_missing, but it doesn't look like Action Cable does that at a glance. Without knowing more about what you're trying to do, it's hard to say whether to change connections_info to a class method, notify to an instance method, or something else.
This also happens when your redis configuration is not correct.
I am building a Rails 5.0 API and trying to have a print_it class method that runs as_json on an object. (I need a separate method to put complex logic into later)
Whenever I test it, it errors with:
NoMethodError (undefined method print_it for #<Class:0x007f7b7b092f20>):
In Model: project.rb
class Project < ApplicationRecord
def print_it
self.as_json
end
end
In controller: projects_controller.rb
class Api::V1::ProjectsController < Api::ApiController
def index
render json: Project.print_it
end
end
How can I use print_it on an object?
Project.print_it
is calling print_it on the class Project. But, you define print_it as an instance method, not a class method, here:
class Project < ApplicationRecord
def print_it
self.as_json
end
end
You probably want something more like:
class Api::V1::ProjectsController < Api::ApiController
def index
render json: #project.print_it
end
end
Naturally, you'll need to set #project.
To use print_it on an ActiveRecord_Relation called #projects, you could do something like:
#projects.map{|p| p.print_it}
You'll end up with an array.
But that might be expensive, depending on the number of projects and the nature of print_it.
How can I use print_it on an object?
You are 'using' (calling) print_it on an object. Project is an object. Just like #project is an object. You just happen to be calling print_it on an object that doesn't have print_it defined (thus the undefined method error).
I will also note that Jörg W Mittag wishes to say:
I am one of those Ruby Purists who likes to point out that there is no such thing as a class method in Ruby. I am perfectly fine, though, with using the term class method colloquially, as long as it is fully understood by all parties that it is a colloquial usage. In other words, if you know that there is no such thing as a class method and that the term "class method" is just short for "instance method of the singleton class of an object that is an instance of Class", then there is no problem. But otherwise, I have only seen it obstruct understanding.
Let it be fully understood by all parties that the term class method is used above in its colloquial sense.
I have a user model in my application. Now I want to replace some user model coding into 2 categories likely employ.rb and customer.rb under a module users, to avoid more number of codes in a single model. I want to access a method send_mail in customer.rb after a user created.
user.rb
after_create:send_msg_on_order
def send_msg_on_order
Users::Customer.send_mail
end
users/customer.rb
def send_mail
Mailer.send_mail_to_customer.deliver
end
And I am getting undefined method `send_mail' for Users::Customer:Module error.
You have defined send_mail method as instance method but calling it as a class method. Either make it a class method or create an instance of Customer model and call it.
Making the method a class method:
def self.send_mail
Mailer.send_mail_to_customer.deliver
end
If you wish to keep it an instance method, then call it like this:
after_create:send_msg_on_order
def send_msg_on_order
Users::Customer.new.send_mail
end
HTH
You can also call like this
def send_msg_on_order
Customer.send_mail
end
I'm just starting to learn rails. I have a test project and I added a instance method and a class method to a Post model class with some existing sample data.
class Post < ActiveRecord::Base
has_many :comments
attr_accessor :region
def sport
puts "Football"
end
def self.region
puts "West"
end
end
I correctly get "Football" when I run Post.first.sport
But I get nil when I run Post.first.region. Why doesn't rails console return "West"?
Thanks!
Since self.region is a defined as a Class method you should run Post.region to output "West"
See https://stackoverflow.com/a/11655868/1693764 for a good description of Class vs instance methods
It fails because you are using a class method on an instance object.
Do:
Post.region #=> 'west'
When you add 'self.' to a method it becomes a class method. Class method are invoked on the entire class. Instance methods on the other hand are invoked on a instance of the class.
Use class methods when you want methods that are applicable for the entire class. For example a method like find_post_with_most_comments.
Post.find_post_with_most_comments
Use instance method when you are dealing with a particular instance of the class. For example a method like first_comment
#post = Post.find(params[:post_id])
#post.first_comment
attr_accessor adds instance methods to the class.
As per your code, it creates an instance variable called "#region" during initialization of an object of Post, in the getter method.
def region
#region
end
and default value of instance variable is 'nil'. So when you access Post.first.region, it returns the default value of the instance variable.
In 'self.region' code, its defined as class method. So it can be called using the syntax 'Model.class_method'
Thus class methods are called on class objects while instance methods are called on instance objects.
Get a deeper understanding of ruby metaclasses for a complete picture, it will make you more curious about the ruby architecture, and will help you learn more.
We have two Rails models: Person and Administrator. We're disallowing removal of Administrators at the model level:
class Person < ActiveRecord::Base
end
class Administrator < Person
def destroy
raise "Can't remove administrators."
end
end
me = Administrator.new
me.destroy # raises an exception
I'd like to be able to get around this during testing, but only for specific instances created during setup and teardown. I don't want to change the behavior of the class, so class_eval and remove_method aren't feasible.
I tried to redefine the actual instance's #destroy method:
def me.destroy
super
end
or redefine it on the singleton class:
class << me
def destroy
super
end
end
but those still raised the exception. I couldn't figure out how to get it to call the superclass method implicitly. I ended up creating my own destroy! method (since that's not actually a method in ActiveRecord), which sort of violates my desire not to change the behavior of the class:
def destroy!
ActiveRecord::Persistence.instance_method(:destroy).bind(self).call
end
Is there any simple way to tell a single instance method to call its superclass method?
Final Answer: Based on the article Holger Just linked to, I was able to simply call the superclass method explicitly:
def me.destroy
self.class.superclass.instance_method(:destroy).bind(self).call
end
I'd try to refactor the behavior to be more test-friendly. E.g. you could allow an optional parameter to destroy e.g. i_know_what_im_doing that has to be set to true to carry out the destroy. Alternatively you could cancel the destroy with a before_destroy hook like
class Administrator < Person
def before_destroy(record)
# You can't destroy me
false
end
end
In your tests, you can then call Administrator.skip_callback :before_destroy to ignore it and to have a proper destroy.
Finally, you could overwrite / stub the method in your tests. While you say you don't want to modify the class's behavior, you still have to do that (and implicitly do that with your destroy! method today).
I'm not familiar with Ruby metaprograming, so I wont answer if you can a call a method of the super class on an instance without modifying it. But you can create a hook to a superclass method with alias :
class Administrator < Person
alias :force_destroy :destroy
def destroy
raise "Can't remove administrators."
end
end
With this, admin.destroy will raise an exception, but admin.force_destroy will actually call the ActiveRecord destroy.