I have a private model method in my rails app that is so vital to quality of life as we know it that I want to test it using rspec so that it squawks if future tampering changes how it works.
class MyModel < ApplicationRecord
belongs_to something
has_many somethings
after_create :my_vital_method
validates :stuff
private
def my_vital_method
#do stuff
end
end
When I try calling the method in an rspec test I get the following error message:
NoMethodError: undefined method `my_vital_method' for #< Class:......>
Question: How do I call a private model method in rspec?
By definition, you are not allowed to call private methods from outside of the class because they are private.
Fortunately for you, you can do what you want if you use object.send(:my_vital_method) which skips the testing for method call restrictions.
Now, the bigger issue is that you really are making your object more brittle, because calling from outside like that may need implementation details that will get out of sync with the class over time. You are effectively increasing the Object API.
Finally, if you are trying to prevent tampering, then you are tilting at windmills -- you can't in Ruby because I can just trivially redefine your method and ensure that if my new method is called from your anti-tamper checking code, I call the original version, else I do my nefarious stuff.
Good luck.
If your private method needs testing it probably should be a public method of another class. If you find the name of this class define its purpose and then create the method signature you want you would end up with something like
def my_vital_method
MyVitalClass.new.do_stuff
end
and now you can write a spec for do_stuff.
Related
I'm having an issue with stubbing out a call to a service object (QuickbooksService) from an AR object. As far as I can tell the method should be stubbed properly and return the value I'm specifying but when I run the spec I can see the method being called and failing.
Here's the class definition of the object I'm testing.
class Order < ActiveRecord::Base
def create_invoice
QuickbooksService.new(:estimate).create_invoice(arg1, arg2, arg3)
end
end
And from order_spec
describe("#create_invoice") do
expect(QuickbooksService.new(:estimate)).to receive(:create_invoice).and_return(1)
end
I've also tried
allow(QuickbooksService.new(:estimate)).to receive(:form_invoice_create).with(anything()).and_return(1)
So instead of returning 1 the create_invoice method is being executed inside of QuickbooksService. Any insight would be appreciated!
The problem you are having is that you are stubbing a seperate instance. i.e When you define the expectation, you telling it to expect that a particular instance receives a call to the method, but when the code is executed, it is creating a different instance. What you need to do is allow any instance to receive the method call. Something like allow_any_instance_of(QuickbooksService).to receive(:invoice_create) will work, but it is much better practice to create a double, something like:
let(:quickbooks_service) { instance_double(QuickbooksService) }
describe("#create_invoice") do
before { allow(quickbooks_service).to receive(:create_invoice).and_return(1) }
it "Creates quickbook invoice" do
order.create_invoice
expect(quickbooks_service).to have_received(:create_invoice)
end
end
See: https://relishapp.com/rspec/rspec-mocks/docs
The problem is that you are instantiating the class while stubbing it allow(QuickbooksService.new(:estimate)).to receive(:form_invoice_create).with(anything()).and_return(1)
Try this:
allow_any_instance_of(QuickbooksService).to receive(:form_invoice_create).with(anything()).and_return(1)
I have a rails 4 app that has an alert model and tests associated to each alert.
When a new alert is created I have a an after_create filter that uses an instance method to create a new test:
class Alert < ActiveRecord::Base
has_many :tests
after_create :create_test
private
def create_test
#bunch of code using external api to get some data
Test.create
end
end
I also have a cron job that I want to use to create a new test for each alert. My plan was to have a class method to do that:
def self.scheduled_test_creation
#alerts = Alert.all
#alerts.each do |a|
a.create_test
end
end
That won't work because the instance method is private. I know I can get around this using send for example. Or I can make the methods public. Or I can rewrite that bunch of api code in the instance method.
I am just not sure what the best way would be. I don't want to write the same code twice and I want to make sure is good practice. Maybe in this case the methods don't have to be private - I know the difference between public/private/protected but I don't really understand when methods should be private/protected.
Any help would be greatly appreciated
I like service classes for interactions between multiple models. Callbacks can make the logic quite hard to follow.
Eg:
class AlertCreator
def initialize(alert)
#alert = alert
end
def call
if #alert.save
alert_test = TestBuilder.new(#alert).call
alert_test.save
true
end
end
end
class TestBuilder
def initialize(alert)
#alert = alert
end
def call
# external API interaction stuff
# return unsaved test
end
end
Inside your controller, you'd call AlertCreator.new(#alert).call instead of the usual #alert.save.
I agree with #SergioTulentsev: while in the long run you may be better served by breaking out this logic into a service class, in the short run you simply shouldn't make a method private if it needs to be called outside of the instance.
In some cases you actually want to access a private method, for example when verifying object state during tests. This is easy to do:
#alert.instance_eval{ create_test }
You can even fetch or alter instance variables this way:
#alert.instance_eval{ #has_code_smells = true }
In general, if you feel the need to do this, it's a warning smell that your logic needs to be rethunk. Ignoring that sort of smell is what turns Ruby from a wonderful language into a way-too-powerful language that allows you to shoot yourself in the foot. But it's doable.
I understand what these statements do, but not how to refer to them. They exist within a class, outside of that class's methods and perform a variety of functions.
Collectively, what are they called?
These methods are really just class methods. Try this:
class Test
def self.before_create
puts "before_create"
end
before_create
end
The specific use case you mentioned - Rails DSL methods such as before_create, that are only available inside a class body — are often called class macros. Rubys metaprogramming abilities give you multiple ways to build them. A simple one is to make them private:
module Foo
private
def before_create
puts "before_create"
end
end
class Bar
extend Foo
before_create
end
before_create is now accessible inside the class body, but not from outside:
Bar.before_create
NoMethodError: private method `before_create' called for Bar:Class
In pure Ruby terms, they are all just method calls.
However, they do have a common theme. In the way they are constructed and used, you could consider them part of a Domain-Specific Language (DSL) - the ones you list are part of Active Record's DSL for creating data models.
Ruby lends itself well to creating DSL-like mini languages, using mix-ins or a base class in order to provide a set of class methods, which in turn will store data or create methods on the class and instances of it using meta-programming techniques.
Say I have the following code
class Monster
def self.yell
'i am yelling!'
end
def shout_something
Monster.yell
end
end
My yell method is a class method while shout_something is an instance method that calls yell.
Is there anything inherently wrong with doing something like this? For example, is it bad to call a class method from an instance method? I ask because it feels wrong but maybe it's just because I'm a newbie to ruby.
On a side note, I thought doing self.yell instead of Monster.yell would make more sense but it doesn't work.
There's nothing particularly wrong about calling a class method from an instance method. Depending on your intent and how you want people to subclass your Monster, you might want to use this:
def shout_something
self.class.yell
end
So that subclasses can provide their yell class method.
Preface: This is in the context of a Rails application. The question, however, is specific to Ruby.
Let's say I have a Media object.
class Media < ActiveRecord::Base
end
I've extended it in a few subclasses:
class Image < Media
def show
# logic
end
end
class Video < Media
def show
# logic
end
end
From within the Media class, I want to call the implementation of show from the proper subclass. So, from Media, if self is a Video, then it would call Video's show method. If self is instead an Image, it would call Image's show method.
Coming from a Java background, the first thing that popped into my head was 'create an abstract method in the superclass'. However, I've read in several places (including Stack Overflow) that abstract methods aren't the best way to deal with this in Ruby.
With that in mind, I started researching typecasting and discovered that this is also a relic of Java thinking that I need to banish from my mind when dealing with Ruby.
Defeated, I started coding something that looked like this:
def superclass_method
# logic
this_media = self.type.constantize.find(self.id)
this_media.show
end
I've been coding in Ruby/Rails for a while now, but since this was my first time trying out this behavior and existing resources didn't answer my question directly, I wanted to get feedback from more-seasoned developers on how to accomplish my task.
So, how can I call a subclass's implementation of a method from the superclass in Rails? Is there a better way than what I ended up (almost) implementing?
Good question, but you are making it too complicated. Keep in mind a few principles and it should all be clear...
The types will be resolved dynamically, so if a show exists anywhere in the object's class hierarchy at the moment it is actually called then Ruby will find it and call it. You are welcome to type in method calls to anything that may or may not exist in the future and it's legal ruby syntax and it will parse. You can type in an expression that includes a reference to this_will_never_be_implemented and no one will care unless it actually gets called.
Even in Java, there is only one actual object. Yes, you may have a method in the superclass that's calling a method, but it is an instance of the derived class (as well as an instance of the base class) and so you can count on the new show being called.
In a sense, every Ruby class is an abstract class containing stubs for every possible method that might be defined in the future. You can call anything without access qualifiers in the base class or derived class.
If you want a null superclass implementation, you may want to define one that does nothing or raises an exception.
Update: Possibly, I should have just said "call show like any other method" and left it at that, but having come this far I want to add: You can also implement show with Ruby's version of multiple inheritance: include SomeModule. Since you are obviously interested in Ruby's object model, you might implement your attribute with a mixin just for fun.
As you know having a superclass know about subclass functionality is a big no-no, which is why you wanted the abstract method.
What you want to do is define show in your superclass. Then you can call it in the superclass and the subclass will call its own version but the superclass won't throw an error.
class Media < ActiveRecord::Base
def show
# This method should be overloaded in a subclass
puts "Called from Media"
end
def do_something
show
end
end
class Image < Media
def show
puts "Called from Image"
end
end
class Video < Media
def show
puts "Called from Video"
end
end
i = Image.new
i.do_something
=> Called from Image
v = Video.new
v.do_something
=> Called from Video
Simple answer. Just call it. Ruby does not have compile-time checking so there is no one to complain that show isn't defined on Media. If #example is an instance of Image, then any call to #example.show will be sent to Image#show first, wherever it is made. Only if Image#show doesn't exist then the call will be passed on to Media, even if the call originated from code defined in Media
If you want to call show on self from within a method of Media, simply do it. However, make sure self responds to the method call.
class Media < ActiveRecord::Base
def foo
if self.respond_to?(:show)
self.show
else
... // *
end
end
...
end
To avoid the branch, implement show on Media, using the * as the body of show
class Media < ActiveRecord::Base
def foo
self.show
end
def show
...
end
end