Rails: Change index based on selected value in dropdown - ruby-on-rails

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 %>

Related

Search object with a date range

I have a dashboard with a date range, the user can search activy for the period he wants.
I have different models that I manage with the same Dasboard
For example I want to find:
the users registration in a date range
the sales in a date range
etc...
I have buttons that represent the models, on click they render the appropriated partial.
I can search for date range but it only works for the first partial sales in this case, if I click on the next button which is users the search redirect on the first partial sales
This is because the dashboard action show renders by default sales I would need something that renders the current active button...
Could you help please me to find how to do this?
below some code:
dashboard.rb
class Dashboard
attr_reader :date_from, :date_to
def initialize(params)
params ||= {}
#date_from = parsed_date(params[:date_from],Time.now.beginning_of_month.to_date.to_s)
#date_to = parsed_date(params[:date_to], (Date.today + 1).to_s)
end
def article_date_range
Article.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
the show action in dashboards_controller.rb
def show
#button ||= set_button || :sales
#dashboard = Dashboard.new(params[:search])
#articles = #dashboard.article_date_range
#articles = #articles.order('created_at ASC')
end
private
def set_button
button = dashboard__params[:button]&.to_sym and [:sales, :users].include?(button) and button
end
this form and buttons are in the dashboard show.html.erb
<%= form_tag admin_dashboard_path, method: :get do %>
<%= text_field_tag "search[date_from]", #dashboard.date_from, class: "datepicker" %>
<%= text_field_tag "search[date_to]", #dashboard.date_to, class: "datepicker" %>
<%= submit_tag "Rechercher", class: "btn btn-main" %>
<% end %>
#the buttons that "redirect" to the appropriated partial
<% [:sales, :users, :articles].each do |button| %>
<li class="tabs list-inline-item <%= params[:button] == button.to_s ? "tabs__item--active" : "tabs__item--inactive" %>">
<%= link_to admin_dashboard_url(button: button) do %>
<%= button.capitalize %>
<% end %>
</li>
<% end %>
Update
The idea is that the user select a date range and then click on the buttons to see the activity in the selected date range for either articles sales and users (without selecting again the date range)
I think this is because, dashboard__params[:button] is always nil. Because in your search form there is no input field with name set to button. If you are thinking about <%= link_to admin_dashboard_url(button: button) do %>, it's outside the html form.
So method set_button is returning nil.
So the first line of your show action always returns :sales as this is the default value if set_button returns nil.
I would suggest to add an hidden input field in your search form. Then use javascript to prevent the default form submit behavior, set the value of that hidden input field with appropriate value (sales or user), then submit the form using javascript.
Update 1
show.html.erb
<%= form_tag admin_dashboard_path, method: :get do %>
<%= text_field_tag "search[date_from]", #dashboard.date_from, class: "datepicker" %>
<%= text_field_tag "search[date_to]", #dashboard.date_to, class: "datepicker" %>
<%= hidden_field_tag 'button', 'sales', id: 'button' %>
<%= submit_tag "Rechercher", class: "btn btn-main" %>
<% end %>
#the buttons that "redirect" to the appropriated partial
<% [:sales, :users, :articles].each do |button| %>
<li class="tabs list-inline-item <%= params[:button] == button.to_s ? "tabs__item--active" : "tabs__item--inactive" %>">
<%= link_to admin_dashboard_url(button: button) id: "#{button}_click" do %>
<%= button.capitalize %>
<% end %>
</li>
<% end %>
Now using javascript before submitting the search form, set the value of the hidden_filed.
Update 2
show.html.erb
<%= link_to '#', id: "#{button}_click", class: 'model_link' do %>
<%= button.capitalize %>
<% end %>
javascript (assuming you have jQuery)
$(document).ready(function() {
$('.model_link').click(function() {
var buttonName = $(this).attr('id').split('_')[0];
$('#button').val(buttonName);
$('form').submit();
});
});

Spree Dropdown boxes for variant option values

I'm learning Spree 3.0 and I have a setup a test shop that sells shorts.
Shorts has multiple option types: Size, Color, Length
I wanted to change the way it displays the variant options on the frontend from a radio checkbox to a drop down box.
Currently, Spree displays the option types as radio buttons:
I want to change this to use drop down menus for each option type, like this:
I've tried the following:
<%= select_tag "variant_id", options_for_select(#product.variants_and_option_values(current_currency).collect{ |v| ["#{variant_options(v)} #{variant_price(v)}", v.id] })%>
But it simply displays the values of all option types in each tag:
I wanted to know the best way to split the option values into individual dropdown menus?
Any assistance is my much appreciated, thank you.
This is not as easy as it looks since you will be using Spree::OptionValue records instead of variants and at some point you will want to convert back to variants in order to add it to your cart. Combinations might not be possible and/or out of stock so it is highly unpractical to work with option_values.
But nonetheless, you wanted to know how so i set up the following:
#options = Spree::OptionValue.joins(:option_value_variants).where(spree_option_value_variants: { variant_id: #product.variant_ids }).group_by(&:option_type)
This will give you a hash with the keys of the hash being option_types (Size, Color, Length in your case) and the values being arrays of option_values.
You can easily form this into radios like this:
<% #options.each do |option_type, option_values| %>
<%= content_tag :h2, option_type.presentation %>
<% option_values.each do |option_value| %>
<%= radio_button_tag option_type.name, option_value.id %>
<%= label_tag option_value.presentation %>
<% end %>
<% end %>
Or for dropdowns:
<% #options.each do |option_type, option_values| %>
<%= content_tag :h2, option_type.presentation %>
<%= collection_select :variants, option_type.name, option_values, :id, :presentation %>
<% end %>
And in your controller you would want to find a variant matching those 3 criteria, check if it is in_stock, backorderable or track_inventory? is false and respond with errors or an updated cart :)
I hope this helped
This is what I did to solve this problem. It basically takes the variant_id parameter that was controlled by the radio buttons and turns it into a hidden field controlled by jQuery and AJAX with additional notifications.
I hope this helps someone.
config/routes.rb
# Mount the core routes
Rails.application.routes.draw do
mount Spree::Core::Engine, at: '/'
end
# Create new routes
Spree::Core::Engine.routes.draw do
post "products/:product_id/get_variant",
to: "products#toggle_like",
as: "get_variant",
constraints: { :format => /(js)/ }
end
app/models/spree/product_decorator.rb
Spree::Product.class_eval do
# Find the Product's Variant from an array of OptionValue ids
def find_variant_by_options(array)
option_values = Spree::OptionValue.where(id: array)
variants = []
option_values.each do |option_value|
variants.push(option_value.variants.ids)
end
self.variants.find_by(:id => variants.inject(:&).first)
end
end
app/controllers/spree/products_controller_decorator.rb
Spree::ProductsController.class_eval do
# Get the Variant from params[:ids], respond with JavaScript
def get_variant
#product = Spree::Product.find_by :slug => params[:product_id]
#variant = #product.find_variant_by_options(params[:ids].split(','))
respond_to do |format|
format.js
end
end
end
app/views/spree/products/get_variant.js.erb
// Update hidden field #varient_id's value.
$("#variant_id").val("<%= #variant.id %>")
// Update price
$(".price.selling").html("<%= number_to_currency #variant.price %>");
<% if #variant.in_stock? && #variant.available? %>
// If in stock and available
$("#add-to-cart-button").prop("disabled", false); // Enable button
$(".out-of-stock").hide(); // Hide 'out of stock' message
<% else %>
// Otherwise
$("#add-to-cart-button").prop("disabled", true); // Disable button
$(".out-of-stock").show(); // Show 'out of stock' message
<% end %>
app/views/spree/products/_cart_form.html.erb
<%= form_for order, url: populates_orders_path do |f| %>
...
<% if #product.variants_and_option_values(current_currency).any? %>
<div id="product_variants" class="col-md-6">
<h3 class="product-section-title"><%= Spree.t(:variants) %></h3>
<% #product.option_types.each do |option_type| %>
<%= f.label "option_type_#{option_type.id}", option_type.name %>
<br>
<%= f.select "option_type_value_#{option_type.id}",
option_type.option_values.all.collect { |v| [ v.name, v.id ] },
{ include_blank: true },
{ class: "form-control" } %>
<br>
<% end %>
<%= hidden_field_tag "variant_id", value: "0" %>
...
</div>
<% else %>
<%= hidden_field_tag "variant_id", #product.master.id %>
<% end %>
...
<span class="price selling"
itemprop="price"
content="<%= #product.price_in(current_currency).amount.to_d %>">
<%= display_price(#product) %>
</span>
...
<%= button_tag class: "btn btn-success",
id: "add-to-cart-button",
disabled: #product.variants.any?,
type: :submit do %>
<%= Spree.t(:add_to_cart) %>
<% end %>
...
<span class="out-of-stock" style="display: none;">
<%= Spree.(:out_of_stock) %>
</span>
<% end %>
<script>
// Call AJAX if all options are selected, otherwise clean up.
$("#product-variants select").change(function(){
var option_value_ids = [];
$("#product-variants select").each(function(){
option_value_ids.push($(this).val());
});
if(option_value_ids.indexOf("") == -1){
$.ajax({
url: "<%= get_variant_path(#product) %>?ids=" + option_value_ids.join(','),
method: "post"
});
}else{
$("#variant_id").val("0");
$("#add-to-cart-button").prop("disabled", true);
$(".out-of-stock").hide();
}
});
</script>

How can I dynamically update search results based on form input in Rails?

I want site visitors to be able to view nearby shows within a radius that can be input via dropdown form. I have a view that displays nearby shows using the Geocoder gem:
<h3> Shows near <%= request.location.city %> </h3>
<%= form_for #nearby_shows.first do |f| %>
<p> Radius (in miles): <%= f.select(:radii, [10,20,30,40,50], {},
:style => "width:50px", :selected => f.object.radii, :onchange =>
"location.href = '#{shows_path}'") %> </p>
<% end %>
<ul class="users">
<% #nearby_shows.each do |nearby_show| %>
<li>
<%= link_to nearby_show.show_name, nearby_show %>
</li>
<% end %>
</ul>
Right now the selection doesn't affect anything, and the selection isn't remembered in the form when the page refreshes.
The model, show.rb contains:
attr_accessor :radii
And the shows controller contains:
def index
#shows = Show.all
#radii = 50
#nearby_venues = Venue.near("Boulder, CO",#radii,:select =>
"id").to_a
#nearby_shows = Show.where(show_venue: #nearby_venues)
end
In production, I'll be using request.location.city, but in development I'm just using "Boulder, CO" as an example.
How can I set #radii using the input select form? I am concerned that form_for will not permit me to change a variable for the list of entities #nearby_shows.
If you want a fast AJAX solution, here's what I would do
First, add an ID to your list so it's easy to manipulate
<ul id="my_id" class="users"></ul>
I really don't understand why you need that <%= form_for #nearby_shows.first %> for ? If I understand well, you just want to show a select, and update the list of nearby shows based on what the user selects ?
routes.rb
resource :shows do
get 'update_nearby', on: :collection, constraints: { format: 'js' }
end
# GET /shows/update_nearby, meant to be used only with AJAX
your_view.html.erb
<%= form_tag update_nearby_shows_path, remote: :true do |f| %>
<p> Radius (in miles): <%= select_tag(:radii, [10,20,30,40,50], {},
:style => "width:50px", :selected => #radii, :onchange =>
"location.href = '#{shows_path}'") %> </p>
<% end %>
<!-- On Submit, it will request a JS response
You can add some JS to submit the form everytime the select changes -->
Add some JS specific respone
your_controller.rb
def update_nearby
find_nearby_shows
end
private
def find_nearby_shows
#radii = params[:radii] ? params[:radii] : 50
#nearby_venues = Venue.near("Boulder, CO",#radii,:select =>
"id").to_a
#nearby_shows = Show.where(show_venue: #nearby_venues)
end
update_nearby.js.erb
<% if #nearby_shows %>
// Empty the list
var id = $("#my_id").empty()
<% #nearby_shows.each do %>
// Add one <li> per show
$("<li>", { 'html': "<%= escape_javascript(link_to(nearby_show.show_name, nearby_show)) %>"}).appendTo(id)
<% end %>
<% end %>
Bonus : you said you wanted to save the radius ? You can actually try to add it to the user session
def index
...
#radii = session[:saved_radii] ? session[:saved_radii] : DEFAULT_RADIUS
...
end
def update_nearby
find_nearby_shows
session[:saved_radii] = #radii
end
But if you really want to save it for the user, you should have a preferred_radii field in your User model

Parameters not being read even though they appear to be passed

I'm making a form that creates more than one record for the user depending on how many items the user decides to check off in the form using checkboxes.
Currently, I'm running into an error where param is missing or the value is empty: itemrecord even though in the log, it appears that params are passing through:
{"utf8"=>"✓", "authenticity_token"=>"m2NMruoFRr6lpsuVMK9UthlY0bsJsPmf1LWce2uKaH4=", ":item_name"=>["Backpack", "Water filter"], "commit"=>"Go!"}
Model relationship is that a User has_many :inventories
Controller code:
def create
#itemrecord = #current_user.inventories.build
items_to_be_saved = []
inventory_params.each do |i|
items_to_be_saved << ({ :signup_id => #current_user.id, :item_name => i })
end
if Inventory.create items_to_be_saved
flash[:success] = "Thanks!"
redirect_to root_path
else
render new_inventory_path
end
end
def inventory_params
params.require(:itemrecord).permit(:item_name)
end
View code:
<%= form_for #itemrecord do |f| %>
<!-- In case you're wondering, the #wishlist below is basically a hash of categories of items and items. This hash is updated in the controller, and then used by multiple views to create the same table of items. -->
<% #wishlist.each do |category, list| %>
<div class="col-xs-2">
<div class="form-group box">
<h5> <%="#{category}"%> </h5>
<% list.each do |thing| %>
<%= check_box_tag ":item_name[]", "#{thing}" %>
</br>
<% end %>
</div>
</div>
<% end %>
<%= f.submit "Go!", class: "btn btn-primary btn-large btn-block" %>
</div>
<% end %>
By the way I also tried changing :item_name to :item_names to account for the array based on what else I read on SO, but that didn't fix it either
Take a look at your inventory_params function. You're saying that you require an itemrecord, and permit an item_name attribute. Observe:
def inventory_params
params.require(:itemrecord).permit(:item_name)
end
However, in the parameters being passed, there is no reference to an itemrecord object whatsoever, but there is a reference to item_name. A quick change to your inventory_params method, removing the :itemrecord requirement and instead requiring :item_name, will fix your issue.
def inventory_params
params.require(:item_name)
end
While this isn't necessarily the best way to go about doing this (I'd suggest reading up on your Active Record Form Helpers), it should solve your issue.

Rails Form element to convey two values

I have a form that takes bookings for an event for people. The form displays events vertically, and a name & checkbox for each of the possible people next to each event.
How should I best convey the two pieces of information that i need per checkbox? that is, the event_id and the person_id
I'm not totally sure wether I got you right. This is the model I assume you're talking about:
# event.rb
class Event
has_many :people
scope :possible_people, -> { #whatever .. }
end
# person.rb
class Person
belongs_to :event
end
# events_controller.rb
class EventsController
def index
#events = Event.all
end
end
And this might be a possible solution to change an events relation to people:
# index.html.erb
<ul id="events">
<% #events.each do |event| %>
<li class="event">
<%= form_for #event do |form| %>
<% event.possible_people.each do |person| %>
<%= check_box_tag "event[person_ids][]", person.id, #event.people.include?(person) %>
<% end %>
<%= f.submit_tag 'Save Event' %>
<% end %>
</li>
<% end %>
</ul>
The important part is <%= check_box_tag "event[person_ids][]", person.id, #event.people.include?(person) %> where you actually change the the relation of a specific person to the event.
Good luck ;)
Well, you can try out something like below line, I am assuming you have a multiselect checkboxes and i am passing a Hash of event_id => plate_id as value to checkbox.
<%= check_box_tag 'booking[event_people_ids][]', {booking.event_id => booking.plate_id} %>
You will get the value in params as:
booking => {event_people_ids =>["{"72"=>"3"}}
I ended up doing this:
<%= check_box_tag "booking[]", "#{event.id}-#{person.id}" %>
and then in then to process them:
params[:booking].each do |booking|
booking = booking.split('-')
a = {
:booking_id => #booking.id,
:person_id => booking[1],
:event_id => booking[0]
}
Appointment.create(a)
end
I was hoping for a more railish way to do it but this works.

Resources