views:

35

answers:

2

As part of learning ruby/rails, I'm trying to implement http://github.com/professionalnerd/simple-private-messages into my application from scratch, instead of just installing the plugin. (I know, causing myself trouble but it's a good learning experience.)

I created the model, and the associations seem ok and I can create new messages fine and they show up in the recipients mailbox. But if I click to view one message (calls the show method in the message controller) it trips up on looking for a method called 'read' eg.

undefined method `read' for #<Class:0xb6f9ef78>

Where should I put the 'read' method. In private_messages_extensions.rb (the plugin source) it has:

  module ClassMethods
    # Ensures the passed user is either the sender or the recipient then returns the message.
    # If the reader is the recipient and the message has yet not been read, it marks the read_at timestamp.
    def read(id, reader)
      message = find(id, :conditions => ["sender_id = ? OR recipient_id = ?", reader, reader])
      if message.read_at.nil? && reader == message.recipient
        message.read_at = Time.now
        message.save!
      end
      message
    end
  end

  module InstanceMethods
    # Returns true or false value based on whether the a message has been read by it's recipient.
    def read?
      self.read_at.nil? ? false : true
    end

What is the relationship between Class methods and Instance methods in relation to inserting directly into my own messages controller & model? I thought I inserted

def read(id, reader)
...
end

into the model, but the read? method in the instance methods section of the plugin code is confusing me and I continue to get the error on viewing a message.

Help appreciated!

A: 

I haven't looked at the plug-in but based on the standards from other plug-ins, I think you need something like this:

def self.read(id, reader)
  ...
end

This will make it a class method, accessible by calling User.read.

(I am assuming this because of the module ClassMethods block)

Tony Fontenot
A: 

ClassMethods / InstanceMethods is standard Ruby technique that is based on this code:

def self.included(base)
  base.extend ClassMethods
  base.include InstanceMethods
end 

which you will (roughly) find in the original code. By the way there is ActiveSupport::Concern that can simplify the code above (take a look into the documentation for more info)

So whenever the module is included in class then all methods in ClassMethods module will became class methods of the target class and all methods in InstanceMethods will become instance methods of target class.

So your read method is class method, which has to be called as:

m = MyModel.new
m.read?          # => ok, it is an instance method
MyModel.read     # => ok, it is a class method
m.read           # => oops, WRONG! FAILURE! 
                 # You are calling class method through object

Meta-programming rules!

If you rewrite the code without meta thing, you should write it as:

class MyModel
  def self.read
  end

  def read?
  end

end    
pawien
Thank you for the very thorough answer!
Dave