RSPEC - Test to_csv class method - ruby-on-rails

I have this class method in my model:
def self.to_csv
attributes = %w(title)
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |campaign|
csv << campaign.attributes.values_at(*attributes)
end
end
end
I'm looking for good ways to test this method with Rspec. Does anyone have good techniques for this method?

I have a few comments:
I would not use all unless you are in a background job or you know the collection would not be that big
if you really have to use all, then dont use .each use .find_each which would do queries in batches
use factory bot if you can
For the spec itself, I would do:
it "creates expected csv" do
allow(described_class).to receive(:all).and_return([
described_class.new(title: "title1"),
described_class.new(title: "title2")
])
expect(described_class.to_csv).to eq "title\ntitle1\ntitle2\n"
end

Related

How to add links to the individual records in the to_csv method in rails 5 so absolute urls are printed in the csv export?

I'm working on a project in rails and recently watched this great gorails.com episode on exporting data as CSV (https://gorails.com/episodes/export-to-csv). I can get it to work, but I really want to get the absolute url of each of the "tasks" so in the CSV export it would show in each row as a 4th column the link to the "task" eg: "localhost:3000/tasks/1" "localhost:3000/tasks/2" etc. Something like "task_url" as the column header
I haven't found any resource online that helps with this. Any thoughts on how to do this in the most efficient way? Thank you for the help! Here is the working code:
def self.to_csv
attributes = %w{id task_name task_description}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |task|
csv << attributes.map{ |attr| task.send(attr) }
end
end
end
And I would like to do something like this (emphasis added around task_url):
def self.to_csv
#need to declare what task_url is here
attributes = %w{id task_name task_description **task_url**}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |task|
csv << attributes.map{ |attr| task.send(attr) }
end
end
end
You could update your Task model with a #task_url instance method so that calling send on the task instances works. Then next implement the #task_url method with the necessary logic to the build and return the appropriate link for the individual task object. Something like:
class Task
include Rails.application.routes.url_helpers
...
def self.to_csv
attributes = %w{id task_name task_description task_link}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |task|
csv << attributes.map{ |attr| task.send(attr) }
end
end
end
def task_link
task_url(self)
end
end
In this example, the links are constructed using the rails routes helpers. And to access them, we'll need to the include the #url_helpers module since they are not available (by default) in the model layer.
Notice that in the example, the method is called #task_link; the assumption being that the route helper method is already named #task_url and so we avoid overriding it.

What is the idiomatic way to use current_scope or create a new one in model's class method

I want to use to_csv method in a chain like this Company.where(created_at: '2010-01-01').limit(20).to_csv
To do so, I use current_scope and it works fine. But when calling the same method like this Company.to_csv it complains because current_scope is nil.
Right now I'm using (current_scope || where(true)) which does the trick for me, but maybe there's a "proper" way to do it?
def self.to_csv
CSV.generate do |csv|
(current_scope || where(true)).includes(:_address).each do |company|
csv << company.attributes
end
end
end
Ps. I know I could just use current_scope and call Company.all.to_csv, but this is not what's this question is about.
I know I am late to the party. I was trying to find the method corresponding to current_scope and stumbled upon your question (and found my precious current_scope method, so thank you).
I think for your case, you could have just written:
def self.to_csv
CSV.generate do |csv|
includes(:_address).each do |company|
csv << company.attributes
end
end
end
You dont have to use current_scope method as Rails already does that for you.
If you are wondering why I had to go looking for current_scope, I had to chain to current_scope like an n number of times, see this:
def self.search(terms)
terms = Array(terms)
return all if terms.blank?
filtered = current_scope || all
terms.each do |term|
filtered = filtered.search_scope(term)
end
filtered
end
def self.search_scope(term)
# logic for scope on 1 term
# returns a scope
end
So I could call like this: MyModel.search(['test1', 'test2']).limit(5) or MyModel.limit(5).search('test')

What is the best way to test activeadmin classes?

I have simple activeadmin class that looks like this:
ActiveAdmin.register Post do
actions :index
index do
index_columns
end
csv do
index_columns
end
def index_columns
column "Id" do |sp|
sp.id
end
end
end
How will be the best to test this code? Write some integrations specs with capybara or maybe there is some other way?
General idea behind testing gem's functionality - you don't test it.
Gems are (usually) already tested.
I agree with Andrey, but needed to do this for work. Here's how I tested the csv portion.
#csv_doc = StringIO.new
allow_any_instance_of(ActiveAdmin::ResourceController).to receive(:stream_resource) do |aa_controller|
receiver = []
# it's ok to mock this because it's literally their code: https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/resource_controller/streaming.rb#L38
aa_controller.class.active_admin_config.csv_builder.build(aa_controller, receiver)
receiver.each do |fees_as_csv|
#csv_doc << fees_as_csv
end
end
#csv_doc.rewind
csv_string = #csv_doc.readlines.join
csv = CSV.parse(csv_string, headers: true).map(&:to_hash)
expect(csv[0]["FIGURING THIS OUT"]).to eq "SUCKED"

Exporting CSV data from Rails

I'm working to export CSV data from Rails. I'm following the tutorial here: http://railscasts.com/episodes/362-exporting-csv-and-excel?view=asciicast
In my controller I have:
def show
# #company is being provided correctly.
#groups = #company.groups
render text: #groups.to_csv
end
In my group.rb model:
def self.to_csv
Rails.logger.info "Hello World"
CSV.generate do |csv|
csv << column_names
all.each do |product|
csv << product.attributes.values_at(*column_names)
end
end
end
The issue is the browser is outputting just the following:
#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#
The model method appears to be being ignored, even after a Rails restart. What's wrong here?
The to_csv is a class method. Meaning its meant to be called like Group.to_csv. You might want to change the method signature to something like Group.to_csv(groups) instead.
def self.to_csv(groups)
Rails.logger.info "Hello World"
CSV.generate do |csv|
csv << column_names
groups.each do |product|
csv << product.attributes.values_at(*column_names)
end
end
end
Then in show
def show
# #company is being provided correctly.
#groups = #company.groups
render text: Group.to_csv(#groups)
end
I also met this problem, which was already solved by this answer:
https://stackoverflow.com/a/17311372/3458781
Shortly, you have to restart your server. Hope it works for you.
A quick version for rails console that is not for prod but just to have access to the data in excel:
first do :
print TableName.first.attributes.map{|a,v| a}.join(",")
second do :
TableName.all.each{|t| print t.attributes.map{|a,v| a}.join(",") + "\n"}; nil
copy the result of this two call in a csv file

How do you make a method apply to a collection of ActiveRecord objects

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
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
}
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.
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.
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
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 ;-)
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.
I know that it is a very old question but just thought to provide a feedback anyway.
check blog http://blog.zahiduzzaman.com/2013/07/adding-tocsv-method-to-active-record.html
just another way of achieving this

Resources