views:

599

answers:

6

Currently, if I want to apply a method to a group of ActiveRecord objects, I have to structure the call like so:

messages = Message.find(:all)
csv = Message.to_csv(messages)

How can I define the method so it's structured like so?

messages = Message.find(:all)
csv = messages.to_csv

This is the current model code:

require 'fastercsv'
class Message < ActiveRecord::Base
  def Message.to_csv(messages)
    FasterCSV.generate do |csv|
      csv << ["from","to", "received"]
      for m in messages
        csv << [m.from,m.to,m.created_at]
      end
    end
  end
end
A: 

You could create a method on your Message class to do something along the lines of...

In your controller....

@csv_file = Message.send_all_to_csv

In your model...

require 'fastercsv'
class Message < ActiveRecord::Base
  def send_all_to_csv
    @messages = Find.all
    FasterCSV.generate do |csv|
      csv << ["from","to", "received"]
      for message in @messages
        csv << [message.from,message.to,message.created_at]
      end
    end
    # do something with your csv object (return it to the controller
    # or pass it on to another class method
  end
end
mwilliams
A: 

You could define the method directly on the messages object itself, but if you do that, the method would only be available to that specific instance:

def messages.to_csv()

   FasterCSV.generate do |csv|
     csv << ["from", "to", "received"]
     self.each { |m| csv << [m.from, m.to, m.created_at] }   
   end

end

Then you could call it like so:

messages.to_csv

I'm a Ruby newbie, so I'm not sure if this is idiomatic Ruby or not: that is, I'm not sure how common or accepted it is to define new methods directly on object instances, I only know it's possible ;-)

Mike Spross
+1  A: 

FasterCSV patches the Array class and adds a 'to_csv' method to it already, but it doesn't do what you want. You could overwrite it yourself by doing something like:

class Array
  def to_csv(options = Hash.new)
    collect { |item| item.to_csv }.join "\n"
  end
end

Or something along those lines, but that's kind of crappy.

Honestly, it makes more sense the way you've done it as a class method on your model.

Sam Gibson
+5  A: 

The following will call to_csv on all instances included in the messages array.

messages = Message.find(:all)
csv = messages.map { |message| message.to_csv }

In Rails, in Ruby 1.9 or with Symbol#to_proc available through other means, you can also shorten it to:

csv = messages.map(&:to_csv)

The longer form is useful when you want to make a more complex operation:

csv = messages.map { |message| 
  if message.length < 1000
    message.to_csv
  else
    "Too long"
  end
}
webmat
Symbol#to_proc is also available in ruby 1.8.7
artemave
A: 

Put this in a file in lib/. I would recommend calling it something like _base_ext.rb_

require 'fastercsv'
class ActiveRecord::Base
  def self.to_csv(objects, skip_attributes=[])
    FasterCSV.generate do |csv|
      csv << attribute_names - skip_attributes
      objects.each do |object|
        csv << (attribute_names - skip_attributes).map { |a| "'#{object.attributes[a]}'" }.join(", ")
      end
    end
  end
end

After that, go to config/environment.rb and put require 'base_ext' at the bottom of the file to include the new extension. Upon restarting your server you should have access to a to_csv method on all model classes and when you pass it an array of objects should generate a nice CSV format for you.

Ryan Bigg
A: 

If it is isolated to one AR model I would add a to_custom_csv_array instance method

def to_custom_csv_array
  [self.from,self.to,self.created_at]
end

then override find on the class

def self.find(*args)
  collection = super
  collection.extend(CustomToCSV) if collection.is_a?(Array)
end

and in CustomToCSV define the to_custom_csv to generate the csv

module CustomToCSV
  def to_custom_csv
    FasterCSV.generate do |csv|
      csv << ["from","to", "received"]
      csv << self.map {|obj| obj.to_custom_csv_array}
    end
  end
end

This is from memory but should work.

james2m