How to convert data into an Excel file - ruby-on-rails

I am using the CSV class to convert data into an Excel file based on http://railscasts.com/episodes/362-exporting-csv-and-excel.
Upon downloading the Excel file it is not updated with the latest data which I see on my webpage using filters. My Excel file contains data which is being shown when the page first loads.
I tried to debug the problem and tried other gems like xlsx_writer, getting the same result:
def commission_report
today = Date.today
if params[:from_date].present?
from_date = params[:from_date]
to_date = params[:to_date]
elsif params[:filter] == 'monthly'
to_date = today
from_date = today - 30
else
to_date = today
from_date = today - 7
end
#commissions_report = UserOrderHistory.select("user_order_histories.*,SUM(user_order_histories.revenue_sharing) as revenue_total, restaurants.restaurant_name, managers.username,revenue_sharings.revenue").joins("LEFT JOIN revenue_sharings ON revenue_sharings.restaurant_id = user_order_histories.restaurant_id").joins("LEFT JOIN restaurants ON restaurants.id = user_order_histories.restaurant_id").joins("LEFT JOIN managers ON managers.id = restaurants.manager_id").where("user_order_histories.status != ''").where("revenue_sharing > 0").group("user_order_histories.restaurant_id,user_order_histories.deduction_date").where("user_order_histories.deduction_date BETWEEN ? AND ?",from_date,to_date).order(sort_column + " " + sort_direction)
#commissions_report = #commissions_report.as_json
#commissions_report = Kaminari.paginate_array(#commissions_report).page(params[:page]).per(10)
# raise #commissions.inspect
respond_to do |format|
format.html
format.csv { send_data #commissions_report.to_csv }
format.xls #{ send_data #commissions_report.to_csv(col_sep: "\t") }
end
end

When you're making the filtered request to download the CSV, the params are not being parsed:
<%= link_to 'Download CSV', your_path(from_date: params[:from_date], to_date: params[:to_date], filter: params[:filter]) %>
This way your params will get sent back again to your controller.

It's because when you click on:
link_to "CSV", products_path(format: "csv")
It goes to the index method in the products controllers and within that method you're again fetching all the records from the DB:
#products = Product.order(:name)
So all you've to do is pass the collection of filtered products to the CSV format respond. Something like this:
format.csv { send_data #filtered_products.to_csv }

Related

How do I display database calls from Controller into view, to be viewed in HTML

I was handed a project from someone else, it's in Ruby On Rails, which I know VERY LITTLE. Basically, there is an EXPORT button, that the user clicks to send data to a CSV. I am tasked with sending this data to the view to be seen in HTML. (Thinking I could use dataTables). I have tried following examples, such as:
#example = StudentGroup.where(survey_id: #survey.id).order("groupNum")
and then using <%= #example %> in the view just to see the data and I get nothing. (Also extremely new to MySQL). I'll post the method, if ANYONE can help me, I'd very much appreciate it.
def download_results
if (user_signed_in?)
else
redirect_to new_user_session_path
end
#survey = Survey.find(params[:survey_to_view])
filename = #survey.name + " - " + Date.today.to_formatted_s(:short)
require "csv"
CSV.open(#survey.name+".csv", "wb") do |csv|
csv << [filename]
StudentGroup.where(survey_id: #survey.id).order("groupNum")
csv << []
csv << ["Summarized Results"]
csv << ["UCA","Group Number","Criteria 1","Criteria 2","Criteria 3","Criteria 4","Criteria 5","Criteria 6","Criteria 7","Criteria 8","Overall Team Contribution","Average(Would Work With Again)","Average(C1..C8)","Overall Team Contribution MINUS Average(C1..C9)"]
questions = #survey.questions
numQuestions = 0
questions.each do |q|
if(q.question_type != 2 && q.question_type != 4)
numQuestions = numQuestions+1
end
end
groups.each do |g|
answersCount = Answer.where(student_group_id: g.id).count
if(answersCount == numQuestions && answersCount != 0)
othersInGroup = StudentGroup.where(groupNum: g.groupNum, survey_id: #survey.id).order("groupNum")
size = othersInGroup.count-1
arr = []
criteria = SurveyQuestionDatum.where("number > 24 AND number < 35")
multiAvg = 0
teamCont = 0
criteria.each do |c|
avg = 0
othersInGroup.each do |o|
a = Answer.where(survey_question_datum_id: c.id, student_group_id: o.id).first
if(o.uca != g.uca)
if(a.nil?)
size = size-1
else
avg = avg + a.answer[g.uca].to_i
end
end
end
avg = avg.to_f/size
if(c.number == 33)
teamCont = avg
end
if(c.number < 33)
multiAvg = multiAvg+avg
end
arr << avg
end
multiAvg = multiAvg.to_f/8
arr << multiAvg
arr << teamCont-multiAvg
arr.insert(0,g.uca, g.groupNum)
csv << arr
end
end
csv << []
csv << []
csv << ["Raw Student Answers"]
groups = StudentGroup.where(survey_id: #survey.id).order("groupNum")
size = groups.count
csv << ["UCA", "F-Number", "Group Number"]
groups.each do |g|
answersCount = Answer.where(student_group_id: g.id).count
if(answersCount == numQuestions && answersCount != 0)
othersInGroup = StudentGroup.where(groupNum: g.groupNum, survey_id: #survey.id).order("groupNum")
csv << []
csv << [g.uca, g.FNum, g.groupNum]
answers = Answer.where(student_group_id: g.id)
csv << ["Question Number", "Question", "Answer"]
answers.each do |a|
datum = a.survey_question_datum
question = datum.question
#question_types = {"0" => "short", "1" => "paragraph",
#2" => "title", "3" => "fivept", "4" => "fixed",
#5" =>"ranking", "6"=>"tenpoints","7"=>"hundredpoints"}
ansText = ""
if(question.question_type == 0)
ansText = a.answer
elsif (question.question_type == 1)
if(question.rule == 'perMember')
othersInGroup.each do |o|
ansText = ansText+"#{o.uca},#{a.answer[o.uca]},"
end
elsif(question.rule == 'default')
ansText = a.answer
end
else (question.question_type == 3)
othersInGroup.each do |o|
ansText = ansText+"#{o.uca},#{a.answer[o.uca]},"
end
end
ansText = ansText.chomp(',')
ansText = ansText.split(',')
ansText.insert(0,datum.number,question.question_text)
csv << ansText
end
end
end
end
send_file(#survey.name+".csv", :filename => filename+".csv")
end
You need a new controller action. Take a look at http://guides.rubyonrails.org/layouts_and_rendering.html
Create an index (or show, or whatever you want to call it, maybe example) action. Make sure it is in your routes.
http://guides.rubyonrails.org/getting_started.html#adding-a-route-for-comments
do not use the download_results code.
set your #example variable the way you were trying to do.
create a view for your index action
add the data to your index view.
If you put code in your download_results method (action) it will never get rendered because of the send_file method call.
Did you create a brand new controller / action / view? Did you use generators? Have you really practiced doing this setup exactly the way the examples, videos, tutorials say to do it? If you have, you have seen how all the pieces (models, controllers, actions, views) come together. You should have seen how render statements come into play. Do that, exactly as the tutorials say to do it and you will get the idea.
If you want to use the same content that the download action uses, refactor the code to extract a method that is used both actions.
This is related to respond_to part, check the docs.
send_file(#survey.name+".csv", :filename => filename+".csv")
Your code above simply means you click the button, the controller will respond you with a csv file. So, if you want a html, the controller should be able to respond to html as well.

How to merge two objects in Ruby on Rails

I'm trying to retrieve from the database two contents: the first one with the field source equal to "imported" (which means that we import it from the excel spreadsheet), and the second one with source != imported (we create it from scratch). Attached is my code:
def index
add_breadcrumb 'Projects', projects_path
add_breadcrumb #project.name, #project
add_breadcrumb "List #{#category.display_name} Content", project_category_contents_path(#project, #category)
#contents_imported = Content.of_project(#project).with_category(#category).imported.order('contents.created_at asc')
#contents_not_imported = Content.of_project(#project).with_category(#category).not_imported.order('contents.created_at desc')
#page = params[:page]
#contents = #contents_not_imported << #contents_imported
#q = #contents.search(params[:q])
#content = #q.result(distinct: true).page(#page).per(20)
end
#contents_imported = Content.of_project(#project).with_category(#category).imported.order('contents.created_at asc')
#contents_not_imported = Content.of_project(#project).with_category(#category).not_imported.order('contents.created_at desc')
And I want to combine the two results before showing it:
#contents = #contents_not_imported << #contents_imported
but it didn't work. How can I do that?
If both of them are arrays and are having same type of objects you can do Result = Arr1 | Arr1
That also removes the duplicates. Its like boolean UNION. In your case #contents = #contents_not_imported | #contents_imported
The problem is that you want to concatenate results, but you also want to continue treating the combined results as an ActiveRelation (call .search on it). Here's a simpler approach that avoids the need for concatenation in the first place. You will need a more complex ORDER BY clause to accomplish this, however:
#page = params[:page]
#contents = Content.of_project(#project).with_category(#category).
order('CASE WHEN source <> "imported" THEN contents.created_at END desc, CASE WHEN source = "imported" THEN contents.created_at END asc')
#q = #contents.search(params[:q])
Concatenating the arrays is done with the plus sign
You are getting undefined method search for Array because, concatenating will return you an array. And you can't call search method on that Array
EDIT
def index
add_breadcrumb 'Projects', projects_path
add_breadcrumb #project.name, #project
add_breadcrumb "List #{#category.display_name} Content", project_category_contents_path(#project, #category)
contents_imported_ids = Content.of_project(#project).with_category(#category).imported.order('contents.created_at asc').map(&:id)
contents_not_imported_ids = Content.of_project(#project).with_category(#category).not_imported.order('contents.created_at desc').map(&:id)
#page = params[:page]
contents_ids = contents_imported_ids + contents_not_imported_ids
contents = Content.where(content_ids)
#contents = content_ids.collect{|id| contents.detect{|c| c.id == id}}
#q = #contents.search(params[:q])
#content = #q.result(distinct: true).page(#page).per(20)
end
Just create a new Relation with the conditions of imported or not imported, after that, order all the records (if order is important to #contents and #content):
def index
add_breadcrumb 'Projects', projects_path
add_breadcrumb #project.name, #project
add_breadcrumb "List #{#category.display_name} Content", project_category_contents_path(#project, #category)
#contents_imported = Content.of_project(#project).with_category(#category).imported.order('contents.created_at asc')
#contents_not_imported = Content.of_project(#project).with_category(#category).not_imported.order('contents.created_at desc')
#page = params[:page]
imported = #contents_imported.where_values.reduce(:and)
not_imported = #contents_not_imported.where_values.reduce(:and)
#contents = Content.where(imported.or(not_ipmorted)).order('CASE contents.imported WHEN true THEN contents.created_at asc ELSE contents.created_at desc END')
#q = #contents.search(params[:q])
#content = #q.result(distinct: true).page(#page).per(20)
end
Now you can call Ransack#search on #contents because it is an ActiveRecord::Relation. I assume that the imported scope take a field contents.imported with value true.
If I wrote this without errors, this must works.

Rails Render JSON Object instead of entire array

In my controller I have the present code that results in a JSON array, even though there is only one result:
def getSharedSpecial
#result = Campaign.find_by_sql("SELECT
id
,name
,image
,ad_caption
,ad_details
FROM campaigns
WHERE id = " + params[:shared_campagin_id].to_s + "
LIMIT 1;")
respond_to do |format|
format.html
format.json { render json: { special_shared: #result }}
end
end
returns:
"special_shared":[
{
"id":41,
"name":"tester the way",
"image":{
"url":"/uploads/campaign/image/41/Gilded_pic.jpg"
},
"ad_caption":"yfftitu6",
"ad_details":"jku"
}
]
}
As can be seen given the [], this is a JSON array.
How can I create just an object and not an entire array?
The problem is that find_by_sql always returns an array even though you are only looking for a single record. There is no need to use find_by_sql and you've opened yourself to SQL injection attacks by doing so, so just write the finder the traditional way:
#result = Campaign.select(:id, :name, :image, :ad_caption, :ad_details).find(params[:shared_campagin_id])

Rails Controller Display Only Year

In my rails app I have a movies table, a movie has a release_date (date/month/year).
I also have a years table which will display all movies from a specific year
If i create the year 2013, I would like to display all movies from the year 2013
In my years controller, in the :show action, I have this
#movies = Movie.where(:release_date => :ynumber)
But it doesn't show any movies from that year, even though there are movies with a release_date 2013.
I assume that it isnt showing because when I call :release_date, it is displaying (date/month/year). How can I have it display ONLY year?
I've tried using .strftime("%Y") and .strptime("%Y") but these dont seem to work
PS: I am using SQLite3 in development mode and PostgreSQL in production mode
Years#show
def show
#year = Year.find(params[:id])
#those i've tried seperately which havent worked
#movies = Movie.where("extract(year from release_date) = ?", ynumber)
#movies = Movie.where("extract(year from release_date) = ?", :ynumber)
#movies = Movie.where("extract(year from release_date) = ?", 'ynumber')
#movies = Movie.where("extract(year from release_date) = ?", ':ynumber')
#movies = Movie.where("extract(year from release_date) = ?", params[:ynumber])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #year }
end
end
To find all movies by year try this:
1) MySQL:
#movies = Movie.where("YEAR(release_date) = ?", 2013)
2) SQLite3:
#movies = Movie.where("strftime('%Y', release_date) = ?", 2013)
3) PostgreSQL:
#movies = Movie.where("extract(year from release_date) = ?", 2013)
You also can use release_date .to_date.year to get the year, since you have an standard format (date/month/year).
So you can filter using this, similar to Rajarshi Das's suggestion:
#movies = Movie.all.select{|mov| mov.release_date.to_date.year == params[:ynumber]}
OR
#movies = Movie.scoped.select{|mov| mov.release_date.to_date.year == params[:ynumber]}

Slice/Map Ordered Hash

I am writing a "Punch Clock" application for my office.. I am working on the controller for the "TIme Card" view which should list a users punches for a given week, and total DAILY then add the TOTAL for the week. I have figured out how to get the time diff between all of the punches with slice/map, my issue is that when I try to do this on the ordered hash (grouped by days) I get undefined method `created_at' for #, I know I must be missing some syntax somewhere, your help is greatly appreciated...
Here is my controller...
Note that if i call #in_out_lenghts on #punches, this works and gives me the total for the week, but #punches_days gives me an error, therefore I can not keep a running tally....
def mytimecard
#week = params[:week].to_s
if #week == "lastweek"
#punches = Punch.lastweek.where("user_id = ?", params[:user])
else
#punches = Punch.thisweek.where("user_id = ?", params[:user])
end
#punches_days = #punches.group_by { |t| t.created_at.beginning_of_day}
if #punches.count%2 == 0
#in_out_lengths = #punches_days.each_slice(2).map { |a|(a[1].created_at).round(15.minutes) - (a[0].created_at).round(15.minutes) }
#total = ((#in_out_lengths.inject(:+))/60/60)
else
#total = "Can Not Calculate, Odd Number of Punches"
end
respond_to do |format|
format.html # timecard.html.erb
format.json { render :json => #punches }
end
end
group_by will return a hash of days and punches.
{ :day_1 => [ :punch1, :punch2], :day_2 => [ :punch3, :punch4, :punch5 ] }
doing an each_slice and a map will result in some sort of array, but probably not what you meant.
You may have meant to count the number of punches and call something like this
Punch.lastweek.where("user_id = ?", params[:user]).group('date(created_at)')
This would have resulted in the date => punches_count format, at least with mysql.

Resources