Saving date form to ruby variable - ruby-on-rails

I'm trying to make a date range form in my rails app, so the user can input two dates, and these dates will post to a query to return results from an API the application is connected to.
My model looks like this: It allows the first date and second date to default to create last week's date range, but it is supposed to accept options to overwrite the default...
class Day
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :first_day, :second_day
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def self.monday(options = {})
if options[:first_day]
return Date.parse(options[:first_day])
else
d = Date.today
seven_days_ago = (d - 7)
seven_days_ago.beginning_of_week
end
end
#defines the end of last week for the query
def self.sunday(options = {})
if options[:second_day]
return Date.parse(options[:second_day])
else
d = Date.today
seven_days_ago = (d - 7)
seven_days_ago.end_of_week
end
end
def persisted?
false
end
end
Form:
<%= form_for #day do |f| %>
<div class="field">
<%= f.label 'Beginning Date' %>
<%= f.datepicker :first_day, :size => 6 %>
<%= f.label 'End Date' %>
<%= f.datepicker :second_day, :size => 6 %>
</div>
<div class="actions">
<%= f.submit 'Submit' %>
</div>
<% end %>
And controller:
def new
#day = Day.new
end
# POST /days
# POST /days.json
def create
#day = Day.new(params[:day])
if #day.valid?
redirect_to root_url
end
end
I am currently accessing the Day.monday and Day.sunday methods in queries that live in some of my controllers. These values always end up being the default, even if the user posts other dates through the form (the idea is to overwrite the default values).
I would prefer a javascript option - the user inputs two dates, they stick to the methods without refreshing, then when the dates are reset it goes back to default.
Thanks for any help!

Related

Can you specify a date range for chartkick?

I'm using chartkick to create some line charts to visualize data in my Rails app. At the moment, however, it shows ALL of the data in my database. Obviously this isn't always ideal. I'd like to give the user the ability to specify the date range of the data shown. I would use the created_at value of the record.
I cannot find any instructions in the documentation on how to do this.
https://github.com/ankane/chartkick.js?files=1
I have created a date picker which will give me a URL like http://localhost:3000/metrics?utf8=%E2%9C%93&search%5Bdate_from%5D=2019-11-01&search%5Bdate_to%5D=2019-11-17&commit=Search
Can chartkicker somehow access those parameters?
In my code a Club has many DailyMetrics, and I've created a metrics action in the clubs controller.
View
<%= javascript_include_tag "https://google.com/jsapi" %>
<div class="row">
<h1>Metrics</h1>
<div class="pull-right range-query">
<%= form_tag metrics_path, method: :get do %>
<%= text_field_tag 'search[date_from]', #search.date_from %>
<%= text_field_tag 'search[date_to]', #search.date_to %>
<%= submit_tag 'Search', class: 'btn search-button' %>
<% end %>
</div>
</div>
<h3>Total active students</h3>
<%= line_chart #club.daily_metrics.group(:created_at).sum(:total_active_students), library: { animation: { easing: 'easeOutQuad'}} %>
<h3>Lost students</h3>
<%= line_chart #club.daily_metrics.group(:created_at).sum(:lost_students), library: { animation: { easing: 'easeOutQuad'}} %>
<h3>New students</h3>
<%= line_chart #club.daily_metrics.group(:created_at).sum(:new_students), library: { animation: { easing: 'easeOutQuad'}} %>
Controller
def metrics
#club = current_club
#search = MetricSearch.new(params[:search])
#metrics = #search.scopeContr
end
Model for searching for metrics
class MetricSearch
attr_reader :date_from, :date_to
def initialize(params)
params ||= {}
#date_from = parsed_date(params[:date_from], 7.days.ago.to_date.to_s)
#date_to = parsed_date(params[:date_to], Date.today.to_s)
end
def scope
DailyMetric.where('created_at BETWEEN ? AND ?', #date_from, #date_to)
end
private
def parsed_date(date_string, default)
Date.parse(date_string)
rescue ArgumentError, TypeError
default
end
end
Is what I'm trying to do possible?
I posted a link to an great article in your comments, but you can use a gem called gem 'groupdate' as well.
# Gemfile
gem 'groupdate'
Assume your user model has_many :orders and you want to find out their spending over the past 6 month:
# order.rb
def self.monthly_spending
group_by_month(:date, last: 6, current: false).sum('orders.total')
end
In your view:
<%= line_chart current_user.orders.monthly_spending %>
Here is another tutorial how to group by date with chartkick and groupdate gem.
If you want to preselect a from-to-date for query you can setup a datepicker in your frontend and send the params to your controller
def show
orders = Order.where('created_at > %s AND created_at < %s', params[:start_date], params[:end_date])
end
call like above the group_by methods on your prefiltered list of orders.

Filter by date using materializecss on rails

I am trying to show all my items upon loading the page, and if the date is selected (using materializecss datepicker), to show only the date range of items. Showing all the items is easy as I just need to do #all_requests = Request.all, but I have spent two nights trying various methods like form_for and form_tag etc. Currently with the code below it shows only today's items because of the default Date.today, but if i remove the default it gives a nil error, I think because the date has not been selected yet...
Index.html.erb
<%= form_tag requests_path, method: :get do %>
<%= text_field_tag 'search[date_from]', #search.date_from, placeholder: 'From date', class: 'datepicker' %>
<%= text_field_tag 'search[date_to]', #search.date_to, placeholder: 'To date', class: 'datepicker' %>
<%= submit_tag 'Search', class: 'btn waves-effect waves-light formbttn' %>
<% end %>
<div class="row">
<% #all_requests.each do |request| %>
...
Request.search.rb
class RequestSearch
attr_reader :date_from, :date_to
def initialize(params)
params ||= {}
date_from = parsed_date(params[:date_from], Date.today.to_s)
date_to = parsed_date(params[:date_to], Date.today.to_s)
end
def scope
Request.where(:date => 'date_from'..'date_to')
end
def parsed_date(date_string, default)
Date.parse(date_string)
rescue ArgumentError, TypeError
default
end
end
Requests_controller.rb
def index
# if params[:search] == nil
# #all_requests = Request.all
# else
#search = RequestSearch.new(params[:search])
#all_requests = #search.scope
end
Materializecss DOM
<input type="text" name="" id="search_date_from" value="2017-08-13"
placeholder="From date" class="datepicker picker__input" readonly=""
tabindex="-1" aria-haspopup="true" aria-expanded="false"
aria-readonly="false" aria-owns="search_date_from_root">
class RequestSearch
attr_reader :date_from, :date_to
# use keyword args instead
def initialize(date_from: nil, date_to: nil, **kwargs)
# You need to use `#` to set instance vars
#date_from = parsed_date(date_from)
#date_to = parsed_date(date_to)
end
def scope
Request.where(date: date_from..date_to)
end
# reduce the arity by setting the default in the method
# definition
def parsed_date(date_string, default = Date.today.to_s)
Date.parse(date_string)
rescue ArgumentError, TypeError
default
end
end

In Rails, how can I submit a form and request a csv formatted page?

I have a few parts of the solution, but I'm having trouble bringing them together.
I have a page with two text fields (in a form_tag) in which I'll enter a datetime string with the start and end dates of the records I want to download in CSV form.
I can use a submit_tag and get the two dates, but then I don't know how to get the view to tell the controller that I want a CSV, so . I can use a link_to, but then the params get left behind.
The view and controller look a little wonky as I'm trying to figure out how this stuff should work together. I won't ship both a link and a button, for example. I also removed/changed things as needed for brevity/privacy.
show.html.erb:
<%= form_tag do %>
<br/><br/>
<%= label_tag :start_date, "From:" %>
<%= text_field_tag :start_date, nil, size: 40 %>
<%= label_tag :end_date, "To:" %>
<%= text_field_tag :end_date, nil, size: 40 %>
<br/>
<%= link_to "Export Report", report_path(:csv) %>
<%= submit_tag("Generate .CSV", format: :csv) %><br/><br/>
<% end %>
report_controller.rb:
require 'csv'
class ReportController < ApplicationController
def show
if params[:start_date]
#data = get_data(params[:start_date], params[:end_date])
respond_to do |format|
format.html
format.csv
end
end
end
def build_csv_enumerator(header, data)
Enumerator.new do |y|
CSVBuilder.new(header, data, y)
end
end
def download
if params[:start_date]
#data = get_data(params[:start_date], params[:end_date])
respond_to do |format|
format.html
format.csv
end
end
redirect_to action: "show"
end
private def csv_filename
"report-#{Time.now.to_s}.csv"
end
end
class CSVBuilder
attr_accessor :output, :header, :data
def initialize(header, data, output = "")
#output = output
#header = header
#data = data
end
def build
output << CSV.generate_line(header)
data.each do |row|
output << CSV.generate_line(row)
end
output
end
end
download.csv.erb:
<%- headers = ["name", "email", "created_at"] -%>
<%= CSV.generate_line(headers) %>
<%- #data.each do |line| -%>
<%- row = line.values_at(*headers) -%>
<%= CSV.generate_line(row) %>
<%- end -%>
Because you want to download a CSV, the request should be a GET request.
Your link should look like <a download href="/reports/1.csv?from=2017-01-01&to=2017-12-31">Download</a>. I suggest you build this url on browsers (which means using javascript, not rails).
Your rails application must understand the extension .csv. This can be configured in config/initializers/mime_types.rb
Mime::Type.register "text/csv", :csv
Now you can send CSV to the browser
class ReportsController < ApplicationController
def show
respond_to do |format|
# ...
format.csv do
#csv = ...
send_data #csv.to_s # what you send must be a string
end
end
end
end
Links are self-contained; submit buttons (typically) submit forms (with all their non-disabled, name-having values) to the action URL of the form (unless you're doing submission through JavaScript, in which case you can do whatever you want, obviously).
Thus, you don't specify the URL on submit_tag, you specify it on form_tag:
Starts a form tag that points the action to a url configured with url_for_options just like ActionController::Base#url_for.
As one of the answers mentioned, you need to specify the format on the form_tag. It can get tricky as the format needs to be set in the url_options, not in the other form_tag options. For example:
<%= form_tag({controller: 'my_controller', method: 'my_method', format: 'csv'}, method: "get") do %>
...fields
<%= submit_tag 'Submit' %>
<% end %>

Rails: Change index based on selected value in dropdown

I have two models:
class Location < ActiveRecord::Base
has_many :cows
accepts_nested_attributes_for :cows
end
class Cow < ActiveRecord::Base
belongs_to :location
end
Every location has an id, a name and three boolean values. A cow consists of an id, two strings and two dates and it also includes a location_id.
In my cow view I have a dropdown of every location. It gets updated automatically whenever I create, edit or delete a location.
Below the dropdown you can see all cows ever created. Afer that there is a form to add a new cow with the two string fields and two date fields.
Now I want two things: Whenever I select a value in the dropdown, the displayed cows should change. If I select for example location 2, only cows that have a location_id = 2 in the table should be shown. Also when I create a new cow, the id of the selected location should be saved as well (in this example location_id = 2 should be saved in the cow row when I click the button at the very bottom).
This is how the dynamic dropdown looks like in the cow index.html file:
<%= simple_form_for Cow.new do |f| %>
<%= f.collection_select :location, Location.order('name').all, :id, :name, { prompt: "Ort auswählen" } %>
<% end %>
And this is my cow controller:
def index
if (params[:location] && Location.all.collect(&:name).include?(params[:location][:name]))
#cows = Cow.send(params[:location][:name].downcase)
else
#cows = Cow.all
end
end
# (...)
def create
#cow = Cow.new(cow_params)
# (...)
end
private
# Use callbacks to share common setup or constraints between actions.
def set_cow
#cow = Cow.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def cow_params
params.require(:cow).permit(:ohrmarke, :hin, :weg, :stallnummer, :location_attributes => [:id])
end
end
The edited index part just does not work. Perhaps because I have a dropdown for Cow.new and I don't have a submit button. Now the thing is: I need the dropdown (value) for a new cow but also want to display cows that fit to the selected dropdown value. Also I don't want to use a submit button just for the dropdown box. Is there any solution for my problem? I googled so much but just can't find the right answer for my problem.
Edit:
I edited my cows controller and my index page:
cow controller:
def index
#locations_all = Location.all
if (params[:location] && cows = Cow.where(location_id: params[:location]))
#cows = cows
#location = Location.where(id: params[:location])
else
#cows = Cow.all
end
end
# (...)
def create
#cow = Cow.new(cow_params)
# (...)
end
private
# Use callbacks to share common setup or constraints between actions.
def set_cow
#cow = Cow.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def cow_params
params.require(:cow).permit(:ohrmarke, :hin, :weg, :stallnummer, :location_attributes => [:id])
end
cow index.html:
<%= simple_form_for Cow.new do |f| %>
<%= f.collection_select :location, Location.order('name').all, :id, :name, { prompt: "Ort auswählen" } %>
<ul>
<% #locations_all.each do |loc| %>
<li><%= link_to loc.name, #cows.index(location: loc.id) %></li>
<% end %>
</ul>
<%= f.hidden_field :location_id, value: #location_id %>
<table class="striped">
<thead class="thead-default">
<tr>
<th>Ohrmarke</th>
<th>Stallnr.</th>
<th>Hin/Weg</th>
</tr>
</thead>
<tbody>
<!-- ... -->
</tbody>
</table><br>
<%= f.input :ohrmarke, as: :string, input_html: { maxlength: 5 } %>
<%= f.input :stallnummer, as: :string, input_html: { maxlength: 3 } %>
<!-- datepicker not functional yet -->
<input type="date" class="datepicker" id="hin" placeholder="hin">
<input type="date" class="datepicker" id="hin" placeholder="weg">
<button class="btn waves-effect waves-light" type="submit" name="action">Zuordnung speichern
<i class="material-icons right">send</i>
</button>
<% end %>
Drop-down & Content
This is a possible implementation for cows_controller#index.
def index
#locations_all = Location.all
if (params[:location] && cows = Cow.where(location_id: params[:location]))
#cows = cows
#location = Location.where(id: params[:location])
else
#cows = Cow.all
end
end
There are however a few important points to be made and a few problems.
The code you're using for the drop-down assumes you're making a POST request which isn't necessary in this case. The same can be achieved with a simple link_to. In order for this to work, you'd have something similar to this:
<ul>
<% #locations_all.each do |loc| %>
<li><%= link_to loc.name, cows_path(location: loc.id) %></li>
<% end %>
</ul>
Form
In order for the form to create a new Cow using the correct location you'd need to include a hidden_field inside it.
<%= f.hidden_field :location_id, value: #location.id %>
But this introduces a design problem. What happens when you haven't selected any location on the drop-down menu? There are plenty of ways to handle this problem, but my favorite solution is to hide the form in that case.
<% unless #location %>
#form-block ...
<% end %>
This solutions seems ideal if cows can't be created without a specified location.
Edit
Ok, now that I can see you HTML. I can tell you what wrong. Your simple_form_for block is surrounding the whole view. Which is probably not what you want. forms are usually POST requests, so you only need those when you plan to send data, or create a new entry on a table. link_to is a GET request which you can request information to the server. This a small example of how my view would look like:
#drop-down menu
<div id="cow_menu">
<ul>
<% #locations_all.each do |loc| %>
<li><%= link_to loc.name, cows_path(location: loc.id) %></li>
<% end %>
</ul>
</div>
#table
<table class="striped">
# your table logic...
</table>
#new Cow form
<%= simple_form_for Cow.new do |f| %>
<%= f.hidden_field :location_id, value: #location_id %>
<%= f.input :ohrmarke, as: :string, input_html: { maxlength: 5 } %>
<%= f.input :stallnummer, as: :string, input_html: { maxlength: 3 } %>
<%= f.submit "Zuordnung speichern", class="btn waves-effect waves-light"%>
<% end %>

Rails Sunspot gem: Usings facets with multiple model site-wide searches

I'm trying to implement a sitewide search through the powerful Sunspot gem for Rails. This involves a search across multiple, very different models at once. What I WANT to do is use the faceting feature to allow the user to filter their search results on each model, or by default view all on the same page, interspersed with each other ordered by the :boost qualifier. Combining the faceting code from the Sunspot Railscast with the multiple model searching code from another Stackoverflow question (a variation upon the 'Multiple Type' code from the Sunspot documentation) gave me a solution that I'd think would work, but doesn't.
The multiple method search succeeds, but the facets always turn up null. My basic approach is to provide a virtual attribute on each model by the same name, :search_class, that is just the model's class name rendered into a string. I then try and use that as a facet. However, in the view logic the results of the facet (#search.facet(:search_class).rows) is always an empty array, including when #search.results returns many different models in the same query, despite each returned instance having a perfectly accessible Instance.search_class attribute.
I'm using Rails 3.1.0 and sunspot-rails 1.2.1.
What should I do to make this faceting code work?
Controller:
#searches_controller.rb
class SearchesController < ApplicationController
def show
#search = search(params[:q])
#results = #search.results
end
protected
def search(q)
Sunspot.search Foo, Bar, CarlSagan do
keywords q
#provide faceting for "search class", a field representing a pretty version of the model name
facet(:search_class)
with(:search_class, params[:class]) if params[:class].present?
paginate(:page => params[:page], :per_page => 30)
end
end
end
Models:
#Foo.rb
class Foo < ActiveRecord::Base
searchable do
text :full_name, :boost => 5
text :about, :boost => 2
#string for faceting
string :search_class
end
#get model name, and, if 2+ words, make pretty
def search_class
self.class.name#.underscore.humanize.split(" ").each{|word| word.capitalize!}.join(" ")
end
end
#Bar.rb
class Bar < ActiveRecord::Base
searchable do
text :full_name, :boost => 5
text :about, :boost => 2
#string for faceting
string :search_class
end
#get model name, and, if 2+ words, make pretty
def search_class
self.class.name.underscore.humanize.split(" ").each{|word| word.capitalize!}.join(" ")
end
end
#CarlSagan.rb
class CarlSagan < ActiveRecord::Base
searchable do
text :full_name, :boost => 5
text :about, :boost => 2
#string for faceting
string :search_class
end
#get model name, and, if 2+ words, make pretty
def search_class
self.class.name#.underscore.humanize.split(" ").each{|word| word.capitalize!}.join(" ")
end
end
View:
#searches/show.html.erb
<div id="search_results">
<% if #results.present? %> # If results exist, display them
# If Railscasts-style facets are found, display and allow for filtering through params[:class]
<% if #search.facet(:search_class).rows.count > 0 %>
<div id="search_facets">
<h3>Found:</h3>
<ul>
<% for row in #search.facet(:search_class).rows %>
<li>
<% if params[:class].blank? %>
<%= row.count %> <%= link_to row.value, :class => row.value %>
<% else %>
<strong><%= row.value %></strong> (<%= link_to "remove", :class => nil %>)
<% end %>
</li>
<% end %>
</ul>
</div>
<% end %>
<% #results.each do |s| %>
<div id="search_result">
<% if s.class.name=="Foo"%>
<h5>Foo</h5>
<p><%= link_to s.name, foo_path(s) %></p>
<% elsif s.class.name=="Bar"%>
<h5>Bar</h5>
<p><%= link_to s.name, bar_path(s) %></p>
<% elsif s.class.name=="CarlSagan"%>
<h5>I LOVE YOU CARL SAGAN!</h5>
<p><%= link_to s.name, carl_sagan_path(s.user) %></p>
<% end %>
</div>
<% end %>
<% else %>
<p>Your search returned no results.</p>
<% end %>
</div>
This
Sunspot.search(Foo, Bar){with(:about, 'a'); facet(:name)}
translates to the following in Solr
INFO: [] webapp=/solr path=/select params={facet=true&start=0&q=*:*&f.name_s.facet.mincount=1&facet.field=name_s&wt=ruby&fq=type:(Foo+OR+Bar)&fq=about_s:a&rows=30} hits=1 status=0 QTime=1
You can find the exact Solr query in the solr/log/ solr_production.log file
if you notice facet(:name) translated to f.name_s.facet and not f.foo.facet and f.bar.facet. That is why it did not work as you expected.
The following will work but that needs one to create 3 dummy methods in each model. The idea is you need a separate facet line for each of the types.
Sunspot.search Foo, Bar, CarlSagan do
keywords q
#provide faceting for "search class", a field representing a pretty version of the model name
facet(:foo)
facet(:bar)
facet(:carlsagan)
with(:search_class, params[:class]) if params[:class].present?
paginate(:page => params[:page], :per_page => 30)
end
Again, it is always better to look at the actual SOLR query log to debug the search issues. Sunspot makes many things magical but it has its limitations ;-)

Resources