17

I have a class that contains this class method:

def self.get_event_record(row, participant)
  event = Event.where(
      :participant_id   => participant.id,
      :event_type_code  => row[:event_type],
      :event_start_date => self.format_date(row[:event_start_date])
  ).first

  event = Event.new(
      :participant_id   => participant.id,
      :event_type_code  => row[:event_type],
      :event_start_date => self.format_date(row[:event_start_date])
  ) if event.blank?

  event
end

And I also have, in the same class, an instance method:

def format_date(date)
  parsed_date = date.split('/')

  # if month or day are single digit, make them double digit with a leading zero
  if parsed_date[0].split("").size == 1
    parsed_date[0].insert(0, '0')
  end
  if parsed_date[1].split("").size == 1
    parsed_date[1].insert(0, '0')
  end

  parsed_date[2].insert(0, '20')

  formatted_date = parsed_date.rotate(-1).join("-")
  formatted_date
end

I'm getting an 'undefined method' error for #format_date. (I tried it without the self in front, at first). Can you not use instance methods in class methods of the same class?

KL-7
  • 46,000
  • 9
  • 87
  • 74
steve_gallagher
  • 3,778
  • 8
  • 33
  • 51
  • 2
    Why is `format_date` an instance method? It is not using anything from the instance. – tdgs Jun 28 '12 at 13:04
  • Of course, you can't. In order to call an instance method you need an instance of your class. – KL-7 Jun 28 '12 at 13:06
  • @tdgs Good point. I made it an instance method because I thought that making it a class method would imply it was for public use, when the case is it's only for internal use – steve_gallagher Jun 28 '12 at 13:11
  • @KL-7 Of course, you're right. Like in my other comments, my intent was to just have a method that "helps" a class method in its class. Maybe making it a class method doesn't necessarily imply its for use outside the class, or maybe I need to take a different tact altogether – steve_gallagher Jun 28 '12 at 13:14
  • @steve_gallagher, you can make your class method private like [that](https://gist.github.com/2241134889b5aa414f08). – KL-7 Jun 28 '12 at 13:17
  • Thanks to all for helping me not make an ass out of myself at work. – steve_gallagher Jun 28 '12 at 13:23

3 Answers3

30

Short answer is no, you cannot use instance methods of a class inside a class method unless you have something like:

class A
  def instance_method
    # do stuff
  end

  def self.class_method
     a = A.new
     a.instance_method
  end
end

But as far as I can see, format_date does not have to be an instance method. So write format_date like

def self.format_date(date)
   # do stuff
end
tdgs
  • 1,056
  • 1
  • 11
  • 19
  • I have a instance method because is called from a validation, and a class method than was fine to be able to call the instance method, to be DRY – Albert Català May 24 '16 at 14:17
  • Then do something like this `class A; def instance_method; self.class.class_method; end; def self.class_method; -- do stuff -- ; end; end` – tdgs May 26 '16 at 08:57
7

Just create class method

def self.format_date (..)
  ...
end

And if u need instance method, delegate it to class method

def format_date *args
  self.class.format_date *args
end

And i don't think that it is good idea to call instance methods from class scope

Yuri Barbashov
  • 5,407
  • 1
  • 24
  • 20
3

You could do YourClassName.new.format_date(your_date), although I think it's pretty clear you should be restructuring your code - this method probably doesn't belong on an instance. Why don't you extend the Date Class, or make format_date a class method on the class you are using?

EDIT: Here are a few other things to think about with your code:

  • Your whole format_date method goes to a lot of lengths to manipulate dates as strings. Why not use Ruby's Date Class? Using Date.parse or Date.strptime or even "01/01/2001".to_date might be useful depending on your locale
  • Consider extending the String class for your method, if you really need to make your own method:

    class String
      def to_friendly_formatted_date
        Date.strptime(self, "%d/%m/%y")
      end
    end
    "01/08/09".to_friendly_formated_date
    
  • Your class method is crying our for the find_or_initialize_by helper methods:

    self.get_event_record(row, participant)
      find_or_initialize_by_participant_id_and_event_type_code_and_event_start_date(:participant_id => participant.id, :event_type_code => row[:event_type_code], :event_start_date => row[:event_start_date].to_friendly_formatted_date)
    end
    

By god it's long, but it achieves what you're trying to do more elegantly (although I'm open to argument!)

Rob d'Apice
  • 2,416
  • 1
  • 19
  • 29
  • I originally had it as a class method, but, I was thinking (possibly erroneously) that class methods are meant to be used outside of the class, while this method is strictly an internal method. – steve_gallagher Jun 28 '12 at 13:10
  • I think that thought *was* erroneous! Instance methods should relate to the specific attributes of an instance of that class. Your method really has nothing to do with an instance at all. I'd turn it back into a class method. You have a few opportunities to re-factor your code, too - I'll edit my answer. – Rob d'Apice Jun 28 '12 at 13:12
  • @steve_gallagher: You can make class methods private. Take a look at [this](https://gist.github.com/3011405) gist. – Linuxios Jun 28 '12 at 13:30
  • @steve_gallagher Have edited my answer with a few other tips. – Rob d'Apice Jun 28 '12 at 13:32
  • first_or_initialize is recommended atm – Yuri Barbashov Jun 28 '12 at 13:43
  • Rails really must be made of black magic if it can give you dynamically generated methods like that! – Linuxios Jun 28 '12 at 13:44