Accessing instance method from class method - ruby-on-rails

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.

Related

Rails best practice for calling a model method from a different controller

I have Model_a, model_a_controller and Model_b. I have a method in model_b that I would like to call in model_a_controller. But I am not sure what the best practice for doing so would be. This is my method right now:
def method
id = Model_b.method
.... some other stuff
end
Im calling the method directly from the model but is it better rails practice to perhaps make an instance of the model and then call the method from that instance? Something like
def method
#model_b = Model_b
id = #model_b.method
.... some other stuff
end
Im not quite sure what the best practice would be or if my alternative would make sense; Any pointers are welcome.
It depends what you'd like to achieve and what's the context. I can only answer with some quick guiding questions that might be a start point for your own decision making.
I see two different threads in you question:
Class method vs instance method
For this it doesn't matter where you call the method_b. It's about its logic and responsibility. What does this method suppose to do? Do you need a Model B instance (e.g. #instance_B.valid?)? Or is it a behaviour related to class only or all Model B instances (e.g. Model_B.count)? Think about the outcome of this method and what you need to achieve it.
Where to call the method_b
As a rule of thumb, a good practice in rails is to keep skinny controller.
Is there any relationship between model A and model B? Then probably it's better to call the method_b in the model A. Maybe you should consider adding the service layer for that?
https://www.toptal.com/ruby-on-rails/rails-service-objects-tutorial
Consider relationships between models and the controller actions.
There are no strict rules that always work. It depends on the business context, the app design and direction of the development.
If you provide more specific example, then we can give more specific advice and discuss pros & cons of different approaches.
Maybe you can create a service object like this:
In app, create a folder services(app/services)
Inside services folder you can organize all the services for your project.
services/model_b_service/model_b_method.rb
module ModelBService
class ModelBMethod
def initialize
end
def call
.. logic goes here
end
end
So, in your controller:
def method
id = ModelBService::ModelBMethod.new.call
.... some other stuff
end
If you want to pass arguments just ModelBService::ModelBMethod.new(args).call and fetch in initialize method

Rspec test of private model method in rails app

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.

Does this method that changes an instance variable belong in my Rails controller or model?

I have a basic "best practice" question about controllers and instance variables.
Say you have an instance variable in anew or update action in a controller, is it ok to modify that instance variable via a private method in the controller? Or should the method exist in the model?
e.g. in this example below, I need to loop through the attributes of an instance variable, and add or remove something. For example, if I am using nested attributes 3 layers deep and have to remove certain attributes, change them and then add them back in. I know this may seem strange, but assume it is necessary.
def new
#some_thing = SomeThing.new(:some_params)
do_something_to_inst_var # method call
#some_thing.save
end
private
def do_something_to_inst_var
#some_thing.addresses.each do |address|
# modify it in some way
end
end
Or is this bad practice? Should this be a method in the model and should be called like:
#some_thing.do_something_to_inst_var
OR
should we explicitly pass the instance variable to the method like:
def new
#some_thing = SomeThing.new(:some_params)
do_something_to_inst_var(#some_thing) # method call
#some_thing.save
end
private
def do_something_to_inst_var(some_thing)
some_thing.addresses.each do |addresses|
# modify it in some way
end
end
I'm looking for some clarity here, with an example if possible. I'm still learning and trying to improve and I didn't find an answer by searching.
Rails applications should have "thin controllers" and "fat models" for a couple of reasons:
Each object should handle only its own responsibilities. A controller should just be about connecting the web, the the model and the view, which thanks to Rails doesn't take much code. If a controller method refers repeatedly to methods of the same model, it's incorrectly taking on model responsibilities; we say that it's not cohesive or that it has "Feature Envy". It is more likely that if the model changes the controller will have to change in parallel.
It's easier to test models than to test controllers.
Fix it by writing a method in the model that does the model-specific work and call it in the controller (your second option). (Eventually your model will get too fat and you'll have to break it up too, but that's another story.) For example:
class SomeThingsController
def new
#some_thing = SomeThing.new(:some_params)
#some_thing.do_something # method call
#some_thing.save
end
end
class SomeThing
def do_something
addresses.each do |address|
# modify it in some way
end
end
end
Regarding instance variables.
Define them only if necessary. Presumably the one in your example is needed for the view.
Assuming an instance variable is justified at all, there's no reason not to refer to it in private methods of the class that contains it. That's what they're for. So your first option (referring directly to the instance variable) is a bit better than your third option (passing it in). But, as discussed above, extracting a model method is better than both of the other two options.
In my opinion Modifying #instance_vars from private method is okay if your controller is just 100 lines long.
Imagine a scenario where there are 500 LOC in your controller and after a struggle of a couple of hours you found out that the #intance_var is being modified by some private method.
Helpful tips:
create small private methods with single responsibility
put ! at the end of method_name! indicating that it modifies something. Specially this is helpful when you see my_private_method!, ! makes you realize that its modifying something.
lets not put code in controller that do not belong here.
There is one more option:
In Controller:
def new
#some_thing = SomeThing.new(:some_params)
#some_thing_modified = #some_thing.modify_somehow(params)
#some_thing_modified.save
end
In SomeThing Model:
def modify_somehow(params)
result = self.clone
# ... modify result ...
return result
end
Because modify_somehow is now pure function (assuming you don't do anything in ... modify result ... part, that makes it impure), what you gain here is Referential transparency. Main benefit of referential transparency is that you can determine what function/method invocation will do, only by looking at its arguments, and get result of its work only via return value, and not via side effects. This makes your code more predictable, which in turn makes it easier to understand and debug.
There are of course disadvantages: Because you create new object this option can be less performant, it's also more verbose than its alternatives.
Functional programming concepts, like referential transparency, are not very popular in Rails community (probably because of how OO-centric Ruby is). But referential transparency is there if you want it, with its pros and cons.

What are before_create, validates_presence_of, has_many etc?

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.

Calling a subclass method from a superclass

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

Resources