DNorthrup
DNorthrup

Reputation: 847

Rails 5 - Export to XL/CSV but through Associations

I've been following this Railscast on CSV/Excel Export but I'm having a bit of an issue with the association logic.

CSV Confusion/Issue

In my Collection Model I have

class Collection < ApplicationRecord
  belongs_to :user
  has_many :card_collections
  has_many :cards, through: :card_collections

  def self.to_csv
    attributes = %w{name}

    CSV.generate(headers: true) do |csv|
      csv << attributes

      all.each do |collection|
        csv << attributes.map{ |attr| collection.send(attr) }
      end
    end
  end

  # validates :user_id, presence: true
end

CSV Export

name
Demo 2
HEHHEHEEHHE

name is the column name Demo 2 is the first Collection HEHHEHEEHHE is the second Collection

I need far more information, but once I get 'one' parameter I can go from there. But this isn't working.'name' will return the name of ALL collections, which isn't quite what I want at all, but I'm also trying to get information about the 'cards' in the collection (Referenced by cards_collection) To give you an example - In console I would type: c = Collection.last c.cards.first.name and I would get the first cards name. I'm unsure if I should be making this self.to_csv function in the Card_collections controller, the Cards controller or what. I'm found a lot of similar questions regarding CSV through associations but nothing that has led me to a solution

Excel Confusion

<?xml version="1.0"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
  xmlns:o="urn:schemas-microsoft-com:office:office"
  xmlns:x="urn:schemas-microsoft-com:office:excel"
  xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
  xmlns:html="http://www.w3.org/TR/REC-html40">
  <Worksheet ss:Name="Card Collection">
    <Table>
      <Row>
        <Cell><Data ss:Type="String">Name</Data></Cell>
        <Cell><Data ss:Type="String">Text</Data></Cell>
        <Cell><Data ss:Type="String">Set</Data></Cell>
      </Row>
    <% @collection.card_collections.each do |collection| %>
      <Row>
        <Cell><Data ss:Type="String"><%= collection.card.name %></Data></Cell>
        <Cell><Data ss:Type="String"><%= collection.card.text %></Data></Cell>
        <Cell><Data ss:Type="String"><%= collection.card.set %></Data></Cell>
      </Row>
    <% end %>
    </Table>
  </Worksheet>
</Workbook>

Is a sample script - I actually have this wrapped in an 'each' because if they choose Excel I'll allow them to get one sheet per 'collection'.

To give you an idea of the relation, my Collection View looks like -

<p id="notice"><%= notice %></p>
<div class="center"><%= @collection.name %></div><br />
<div class="center">Made By: 
<strong>
  <% if @collection.public %>
    <%= link_to @collection.user.name, {:controller => "users", :action => "show", :id => @collection.user.id} %><br />
  <% else %>
    Anonymous<br />
  <% end %>
</strong>
Total Cards: <%= @collection.card_collections.sum(:card_counts) %>
Distinct Cards: <%= @collection.card_collections.count %> <br />
</div>
  <% @collection.card_collections.in_groups_of(3, false).each do |group| %>
  <div class='row'>
    <% group.each do |card| %>
      <div class='col-sm-6 col-md-4'>
        <%= image_tag(card.card.image_url, class: "img-responsive") %>
        <h3>
          <%= link_to card.card.name, {:controller => "cards", :action => "show", :id => card.card.id }%>
        </h3>
        <div><%= card_text_swap(card.card.text) %></div>
        <div><span class='cardLabel'>Set</span>: <i class="ss ss-<%= card.card.set.downcase %> ss-3x ss-<%= card.card.rarity.downcase %>"></i>  
               <span class="setName"><%= card.card.setName %></span>
        </div>
        <div>Total in Collection: <%= card.card_counts %></div>
      </div>
    <% end %>
  </div>
  <% end %>


<%= link_to 'Edit', edit_collection_path(@collection) %> |
<%= link_to 'Back', collections_path %>

I struggled in making the view originally but finally got it working, and I'm trying to use the same logic in the Excel export, but it's not rendering properly. (Immediate nil errors, so I think this might be a failure on my part.)

Additional Info All of this is stored within ActiveDirectory, and being rendered to a view currently. Unsure what other information I should offer.

New Excel Issue

Collections_Controller

  def show
    @collection = Collection.find(params[:id])
    respond_to do |format|
      format.html
      format.csv { render text: @collection.to_csv }
      format.xls { send_data @collection.to_csv(col_sep: "\t") }
    end
  end

View

  <%= link_to "CSV", collection_path(format: "csv") %> |
  <%= link_to "Excel", collection_path(format: "xls") %>

CSV is now working - Excel is not.

Error Provided:

ArgumentError in CollectionsController#show
wrong number of arguments (given 1, expected 0)

Upvotes: 0

Views: 1222

Answers (1)

Zaur Amikishiyev
Zaur Amikishiyev

Reputation: 378

"def self.to_csv" defines a class method. If you want to list cards for given Collection you need to define and call (in show method of your collections controller) an instance method.

So I changed your to_csv in the following way:

def to_csv
  attributes = %w{name}

  CSV.generate(headers: true) do |csv|
    csv << attributes
    cards.each do |card|
      csv << attributes.map{ |attr| card.send(attr) }
    end
  end
end

This is how to call to_csv in your controller:

class CollectionsController < ApplicationController
  def show
    @collection = Collection.find(params[:id])
    respond_to do |format|
      format.html
      format.csv { render text: @collection.to_csv }
    end
  end
end

for Excel:

<?xml version="1.0"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
  xmlns:o="urn:schemas-microsoft-com:office:office"
  xmlns:x="urn:schemas-microsoft-com:office:excel"
  xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
  xmlns:html="http://www.w3.org/TR/REC-html40">
  <Worksheet ss:Name="Card Collection">
    <Table>
      <Row>
        <Cell><Data ss:Type="String">Name</Data></Cell>
        <Cell><Data ss:Type="String">Text</Data></Cell>
        <Cell><Data ss:Type="String">Set</Data></Cell>
      </Row>
    <% @collection.cards.each do |card| %>
      <Row>
        <Cell><Data ss:Type="String"><%= card.name %></Data></Cell>
        <Cell><Data ss:Type="String"><%= card.text %></Data></Cell>
        <Cell><Data ss:Type="String"><%= card.set %></Data></Cell>
      </Row>
    <% end %>
    </Table>
  </Worksheet>
</Workbook>

And generally when you want to access cards for given @collection just use cards relation and you will not need extra .card when you access attributes of given card.

Upvotes: 2

Related Questions