Controllers and downloading files with 'send_data' - ruby-on-rails

I can create both xl and csv files formats fine, and would like to create a download link for them within a form.
A user can generate a search for records using this form
=simple_form_for :guestlist_booking_search, controller: 'guestlist_bookings_controller', action: 'index', method: :get do |f|
%fieldset
%legend Guestlist Booking Search
= f.input :lastname
= f.input :start, as: :string, class: "form-control auto_picker1", :input_html => { :class => 'auto_picker1', value: guestlist_booking_search.start.strftime('%d-%m-%Y %H:%M') }
= f.input :finish, as: :string, class: "form-control auto_picker2", :input_html => { :class => 'auto_picker2', value: guestlist_booking_search.finish.strftime('%d-%m-%Y %H:%M') }
= f.submit "Submit"
= f.submit "Download CSV", name: "download_csv"
So the form has two submit buttons, I would like one to process the search and display the results, and the other to process the search and begin downloading an csv file.
So in my index action I have this
def index
if params[:download_csv]
respond_to do |format|
format.html
format.csv { send_data #guestlist_bookings.to_csv }
end
end
end
the guestlist_bookings variable is set in a before block (generating and displaying the search works fine ).
What I can't seem to work out is how to get the file to begin downloading. Currently there is no response from the .xls block. From what I can understand the 'send_data' function is what is used to start a download from the controller.

I think your problem is that it's evaluating the format.html block, not the format.csv block, because you haven't told it that the required format is csv.
Try changing your first line like so - this uses a named route which is nicer for various reasons:
=simple_form_for :guestlist_booking_search, url: guestlist_bookings_path(format: "csv"), method: :get do |f|

Ok so the answer I found here
= f.submit "Go"
= f.submit "CSV", value: :csv, name: :format
basically you need to set the format before the form runs.

Related

Using Rails model method to fetch and return value

Problem
I currently have a method in my models/listing.rb which has a method like this:
def self.lookup_info(val)
# Fetch some data from the internet
return{ price: value1, title: value2, isbn: val }
end
When users plug in val into views/listings/_form.html.erb when creating a new listing, I want to call lookup_info and have it fill in the rest of the form if a result (not nil) is returned.
Setup
controllers/listings_controller.rb
def new
#listing = Listing.new
end
views/listings/new.html.erb
<%= render 'form', listing: #listing %>
views/listings/_form.html.erb (I use div tags in actual code, but don't include below for simplicity)
<%= form_for(#listing, :html => {class: "form-horizontal" , role: "form"}) do |f| %>
<%= f.label :isbn, "ISBN" %>
<%= f.text_field :isbn , class: "form-control" , id: "isbn_field", placeholder: "ISBN (10 or 13 digits - no dashes)", autofocus: true %>
<%= f.label :price, "Price" %>
<%= f.text_field :price , class: "form-control" , id: "price_field", placeholder: "Price" %>
<%= f.label :title, "URL" %>
<%= f.text_field :title , class: "form-control" , id: "title_field", placeholder: "Title" %>
<%= f.submit class: "btn btn-primary btn-lg" %>
<% end %>
What javascript and rails do I need to make the _form.html.erb update when a user types in users_isbn, I call lookup_info(users_isbn) and then get an updated partial such that the values of the fields are set to the results.
Example of response:
<%= form_for(#listing, :html => {class: "form-horizontal" , role: "form"}) do |f| %>
<%= f.label :isbn, "ISBN" %>
<%= f.text_field :isbn , class: "form-control" , id: "isbn_field", placeholder: "ISBN (10 or 13 digits - no dashes)", value: lookup_info(users_isbn)[:isbn] autofocus: true %>
<%= f.label :price, "Price" %>
<%= f.text_field :price , class: "form-control" , id: "price_field", placeholder: "Price", value: lookup_info(users_isbn)[:price] %>
... <!-- Same idea for title -->
<% end %>
Current Start with Javascript
Here is what I have so far:
_form.js.erb (not sure if this would be the right name for the js file)
var isbnField = document.getElementById('#isbn_field').value;
if (isbnField.length == (10 || 13)){
var ajaxResponse = $.ajax({
url: "listings/lookup",
type: 'GET',
data: {isbn: $('#isbn_field').val()}
});
ajaxResponse.success(function(){
alert("Success"); # I would actually want to <%= j render #name of form partial with values %>
});
ajaxResponse.error(function(){
alert("Could not find that ISBN");
});
}
I'd suggest doing it as follows.
listings_controller.rb
# your new action
# your search action
def search
#foo = lookup_info(val)
# You might have to change this line to something like
# self.lookup_info(val) since I can't see the exact application of
# your lookup_info method. But the idea is you would want to put
# the return values into your #foo.
if #foo
respond_to { |format| format.js { render listings/success }
else
respond_to { |format| format.js {render listings/failure }
end
end
Explanation for controller action:
Your form would be submitting to the controller action search. If #foo is valid, meaning if the search is successful, it will render the successful partial view which we will be creating, else we will be rendering a view to handle failed form submissions (assuming you need it).
routes.rb
get "my-search-url", to: "listings#search", as: "my_search_url"
# you can set your own route names
# i assume your form is a get instead of post action
Now for your form, change it to form_with
<%= form_with(url: my_search_url_path) do |f| %>
If you are using Rails 4 instead of Rails 5, you can't use form_with, instead you'd have to use remote: true which would handle the ajax request for you.
Lastly, in your views, you need these 2 components, the js.erb and the partial
success.js.erb
$("#holder-for-search").html("<%= j render partial: 'listings/success' %>");
$("#bigger-holder-for-search").fadeIn("1000");
# note that the file name has to be the same as what your respond_to is rendering in your search action
# also remember to create a failure.js.erb too to handle the failed search
_success.html.erb
# render what you want to show if search is successful
And in your new.html.erb or wherever the form is displayed, simply have your relevant div with the correct ID tags (such as #holder-for-search)
<div id="bigger-holder-for-search" style="display:none;">
<div id="holder-for-search">
</div>
</div>
// and you can consider fading everything else out to fade in your search response
In summary, to reproduce an AJAX response, you need 4 components:
1) In your controller action, you need respond_to { |format| format.js }
2) You need to have a corresponding js.erb in your view (must be named exactly either like the controller action, or the respond_to response)
3) The partial to be rendered by your js.erb
4) A remote: true action. If its a link, you include it in the link, if its a form, you include it in your form. In rails 5, form_with implies remote: true hence you don't need to explicitly type that out.

Rails filter with button or link_to using Ransack

I am developing a rails application using the Ransack gem and below is the code that I have written so far to filter my database which works like a charm. Now what I am trying to do is to add additional button like filter options to my index view (where each button has pre-defined filter value). In other words, once the database is first filtered with a brand name, then I would like users to be able to further filter the database by clicking one of the buttons which has a pre-defined filter value of say 'colour = white', then rails will show all the data with the selected brand name and the colour of white).
Controller
class ProjectsController < ApplicationController
def index
#q = Project.ransack(params[:q])
#projects = #q.result(distinct: true)
#projects_count = #q.result.count
#projects = Kaminari.paginate_array(#projects).page(params[:page]).per(30)
end
Index View
<%= search_form_for #q, remote: true, :builder => SimpleForm::FormBuilder do |f| %>
<%= f.input :brand_id_eq, label: false, collection: Brand.all.map{ |f| [f.name, f.id] }, prompt: "All", input_html: { class: "form-control" } %>
<%= f.button :submit, label: "Search", input_html: { class: "btn btn-primary" } %>
<% end %>
...
<span class="data-sort all"><%= link_to "All",q: {color_cont: 'white'}, :class => 'link-sort', :remote => true, :method => :post %></span>
index.js.erb
$('#projects').html('<%= escape_javascript (render partial: 'index') %>').hide().fadeIn('slow');
The problem that I am facing with this approach using the Ransack gem is that when I click the link_to filter button, it does filter the database with the pre-defined filter value of 'white color' however it resets all the previously selected filter options.
Is my approach correct or any better way to achieve this other than using the link_to option?
SOLUTION
I finally got this working using the rail's scope method and a simple jQuery code as shown in my final code below. One thing that I did initially wrong was that I set the name of the scope same as one of my db column name which caused an error. Once I changed the scope name to 'status1', not 'stock_no', it started to work. Hope this helps.
Defined Scope
class ApplicationRecord < ActiveRecord::Base
scope :status1, -> { where( stock_no = "15251" ) }
def self.ransackable_scopes(auth_object = nil)
[:status1]
end
Index.erb
<%= f.hidden_field :status1 %>
<%= f.submit "Stock", :id => "status1", :onclick => "document.getElementById('q_status1').value = 1;", class: 'btn btn-primary status1' %>
Try this question. It is somewhat what you are trying to do, except instead of a submit button, just make yours a button for filtering. It needs to be inside your search_form_for I'm pretty sure as well. And then write a jquery function to submit when the button is clicked like:
$(document).on("turbolinks:load", function(){
$(".data-sort").on('click', function() {
$("form.your-search-form-classname").trigger('submit.rails');
});
});
UPDATE
Try removing the (boolean = true) attribute from the scope. I tested with a similar app of my own and it worked well.
UPDATE 2
I put the following in my app (where status is a column in by db just like your stock_no) and a got the correct query from my database:
<%= f.hidden_field :stock_no %>
<%= f.submit "Stock", :id => "stock_no", :onclick => "document.getElementById('q_stock_no').value = 1;", class: 'btn btn-primary stock_no' %>
scope :stock_no, -> { where( status: 2 ) }
def self.ransackable_scopes(auth_object = nil)
[:stock_no]
end
Are you sure you are putting the scope in the right model?
Replace
<%= link_to "All",q: {color_cont: 'white'}, :class => 'link-sort', :remote => true, :method => :post %>
with
<%= link_to "All",q: {color_cont: 'white', brand_id_eq: params[:q][:brand_id_eq]}, :class => 'link-sort', :remote => true, :method => :post %>
Here assumption is
Once the database is first filtered with a brand name, then I would like users to be able to further filter the database by clicking one of the buttons which has a pre-defined filter value

Params to controller

In my project I got a view, in this view I have a date input like this:
<%= form_tag installations_path, :method => :csv, :class => 'form-search' do %>
<%= select_date(date = Date.current, options = {}, html_options = {}) %>
<%= submit_tag "Save" %>
<% end %>
In my controller Installation I got a method with the name "csv" this method generate a Csv. Example:
def csv
#consumption = current_user.get_consumptions(params[:year])
respond_to do |format|
format.html
format.csv { send_data #consumption.to_csv }
end
end
When I execute the view I receive this error:
param is missing or the value is empty: installation
The problem is that in my installations_params I have require(:installation) cause I use object installation in others methods.
def installation_params
params.require(:installation).permit(:year,...)
end
What is the best way to resolve this problem ?
The request log may help you firstly.
If you generate the csv as a GET request, then change the code
from:
<%= form_tag installations_path, :method => :csv, :class => 'form-search' do %>
to:
<%= form_tag csv_installations_path, :method => :get, :class => 'form-search' do %>
Make sure the csv_installations_path is exist.
Then, you can get the year params as params[:year] if your input named year.
At last, You don't use any install_params in csv method
Hope it helps you.

redirect from new action to create action with params

Im trying to design a shopping cart. i.e a customer shopping online adds a product to their trolley.
I want to go straight to create action from my new action without going to new.html.erb with pre-set values in my params
Here is what I have so far:
#trolley_id += 1
redirect_to :controller => 'trolleys', :action => 'create', :id => #trolley_id, :something => 'else', method: :post
This redirects me to my index action
To do this with javascript templates, it would look like this:
view
= form_form Trolley.new, remote: true do
-# form goes here
The remote true will submit it as javascript, which will try to render a javascript template.
Your create action can either render :create or let Rails render your template automatically. Since it came in as a javascript request, Rails will render the template with format js.
trolleys/create.js.erb
var html = "<%= j render 'trolley_row', trolley: #trolley %>
$('table.trolleys body').append(html);
I managed to resolve my problem. I created a form in my Product_controller#show that will go straight to my Trolley_controller#create and create an entry in my Trolleys table
<%= simple_form_for [#product, #trolley] do |f| %>
<%= f.input :quantity, collection: 1..12, prompt: "select quantity" %>
<%= f.input :product_id, :as => :hidden %>
<%= f.input :user_id, :as => :hidden %>
<%= f.button :submit, "Add to Basket" %>
<% end %>

ActiveAdmin Passing Resource from Controller to Render

I am building a rails application using ActiveAdmin, and want to build a form for a custom action in the controller. I am trying to pass #listings from a collective_action to the rendered file, allowing me to edit multiple records at once. The #listings are based on an ActiveRecord query which draws record IDs from the URL.
It seems to be successfully accessing the parameters from the URL, and querying the database. When the form is served to the browser however, it is not able to produce the values of listing.title. Any ideas?
Here is my Listing.rb:
ActiveAdmin.register Listing do
collection_action :batch_upload do
ids = params[:id]
#listings = []
#listings = Listing.find(ids)
render template: 'listings/edit_batch'
end
end
Here is my edit_batch.html.haml:
= semantic_form_for :listing, :url => {:controller => 'listings', :action => 'batch_upload'}, :html=>{:method=>:put} do |f|
-#listings.each do |listing|
=f.hidden_field :id, :value => listing.id
=f.input :title, :value => listing.title
=f.submit "Submit"
If the form is correctly displaying listing.id but not listing.title then I suspect the record does not have title set, or listing does not have a title attribute.
To check, run the Rails console and find the record using the id from the form:
$ Listing.find(1)
Check the returned object to see whether it is missing the title.
I changed the code to the input so that it accesses its html directly and it worked:
=f.input :title, :input_html => { :value => listing.title }
Using Formtastic's inputs block might help simplify the inputs for each listing. Doing so will allow the form to create attribute fields for each Listing object.
# edit_batch.html.haml
= semantic_form_for :listing, :url => {:controller => 'listings', :action => 'batch_upload'}, :html=>{:method=>:put} do |f|
- #listings.each_with_index do |listing, index|
f.inputs for: listing, for_options: { index: index } do |l|
= l.input :id, as: :hidden
= l.input :title
= f.submit "Submit"
The :for and :for_options scope inputs fields to a specific object.

Resources