Use instance method in ActiveRecord where clause - ruby-on-rails

I have an instance method that has logic I want to use in a query. My attempts have not worked. I'd rather not duplicate the logic inside is_thing when building my where clause.
class Foo < ActiveRecord::Base
def is_thing?
#... some logic
end
end
I tried
Foo.where(is_thing?)
and got
NoMethodError: undefined method `is_thing?' for main:Object

The Approach I would recommend
I do believe that method is not good practice (to chain a where query). It will only add unnecessary complexity.
The better approach is using scope
scope :is_thing?, -> { where(thing: true) }
and call it
Foo.is_thing?.where()
The Why
The reason it is returned
NoMethodError: undefined method `is_thing?' for main:Object
Is because is_thing? is instance variable of Foo
Instance variable can be called on an instance of the class. And not availabe on main object.
You, however could do
Foo.where(Foo.new.is_thing?)
It is posible to use that if you convert is_thing? to class method. e.g
def self.is_thing?
# code
end
and use it this way
Foo.where(Foo.is_thing?)

try this:
class Foo < ActiveRecord::Base
def is_thing?
#... some logic
end
def things
Foo.where('thing_check = ?', self.is_thing?)
end
end
create another instance method to wrap your where query. Then access it as Foo.last.things

Related

Rails Print JSON with Class Method

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.

How to call another method from a self method with ruby?

# 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.

Access a instance method from other model in ruby on rails?

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

Redefine a single method on an instance to call superclass method

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.

Can I define_method in rails models?

My rails model has code that is attempting to define_method(method_name) inside the model.
I keep getting:
NoMethodError: undefined method `define_method'
What am I doing wrong? Am I doing this in the wrong place. I need this method attached to this model. Where else can I define this method?
EDIT:
For those asking to see the code:
for field in rdev_fields
next if self.attributes.include?(field)
count = count + 1
rdev_hash[field.to_sym] = self.attributes["attribute#{count}"]
if !self.respond_to?(field) then
define_method("#{field}") do
self.send("attribute#{count}".to_sym)
end
end
end
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 :-)
If you came here searching for how to dynamically define a CLASS method, because define_method wasn't working (because it defines INSTANCE methods), here is your answer:
Use define_singleton_method :)
was able to cobble this together. Very little understanding of what's actually going on though.
My instance method foo is opening the class and defining bar on it so that I can then call that on my instance. More experienced folks will let us know if this is opening a can of worms at the same time.
Would be useful to know your specific use for this though.
class User < ActiveRecord::Base
def foo
(class << self; self; end).class_eval do
define_method(:bar) {puts "bar"}
end
end
end
u = User.first
u.foo
u.bar #=> "bar"
The answer to your question is "yes, you can". As for why it's not working for you - it's impossible to say for sure why, if you don't provide some context for the code.

Resources