0

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

6
  • 2
    When another class needs a method, it obviously shouldn't be private. Or it's in a wrong class. Commented Apr 29, 2015 at 15:51
  • They both are part of the Alert class. I am not sure if that's how it should be though. I use whenever to schedule a cron job that calls Alert.scheduled_test_creation Commented Apr 29, 2015 at 15:53
  • Seems that your Alert class is doing too much. I'm pretty sure that create_test method will look better as a TestBuilder class. Commented Apr 29, 2015 at 15:56
  • The question sounds to me as “I accidentally and likely mistakenly set the method access level to private. Now I can’t call it from outside. Help!” :) Commented Apr 29, 2015 at 15:57
  • @SergioTulentsev That makes a lot of sense actually. Where would that class go? Commented Apr 29, 2015 at 16:01

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.