I have an 'advanced search' feature in my rails app where users can filter results. the page is located at https://myurl/users/search. The search form filter is working fine and it redirects to https://myurl/users which is the index page showing the filtered results.
My only concern is that the url gets populated with all of the empty parameters when there are no values chosen by the user for those parameters like this https://myurl/users?cert=CCNA&ed=&ex=&job=NetworkEng&lang=&maj=&s=&u=
what I would like to see is just the params that have a value. For example https://myurl/users?cert=CCNA&job=NetworkEng
here is an example of what I have in my view. This will create a button with the search term in it that the user selected and when they click on the button that particular search filter is removed.
I would like to only have the params that the user actually has populated show in the url if possible and hide all of the others. I'm thinking that maybe i have to use hidden_field_tag somehow or possibly check that each value is present before showing in the link_to?
currently if i do not list all of the params in the link_to then when a user clicks on a button to remove one search filter they all get removed.
<% if params[:job].present? %>
<%= link_to users_path( c: params[:c], ex: params[:ex], s: params[:s], sk: params[:sk], lang: params[:lang],
cert: params[:cert], u: params[:u], relo: params[:relo], ed: params[:ed], maj: params[:maj], user_search: params[:user_search] ) , class: "btn btn-outline-danger" do %>
<i class="fa fa-times"></i> <%= "#{params[:job]}" %>
<% end %>
<% end %>
<% if params[:user_search].present? %>
<%= link_to users_path( c: params[:c], ex: params[:ex], s: params[:s], sk: params[:sk], lang: params[:lang],
cert: params[:cert], u: params[:u], relo: params[:relo], ed: params[:ed], maj: params[:maj], job: params[:job] ) , class: "btn btn-outline-danger" do %>
<i class="fa fa-times"></i> <%= "#{params[:user_search]}" %>
<% end %>
<% end %>
example of what's in my users_controller
if params[:user_search].present?
#users = #users.by_keyword(params[:user_search])
end
example of scopes in my user model
scope :by_will_relocate, ->(relo) { where(will_relocate: true).order(updated_at: :desc) if relo.present? }
scope :by_current_job_title, ->(job) { where('current_job_title LIKE ?', "%#{job}%").order(updated_at: :desc) if job.present? }
scope :by_keyword, ->(k) { by_skill(k) | by_language(k) | by_certification_or_cert_authority(k) | by_education_level(k) | by_university_major(k)}
Your problem is that you're extracting keys from params without regard for their values or whether they're specified or not:
c: params[:c], ex: params[:ex], s: params[:s], sk: params[:sk], lang: params[:lang], cert: params[:cert], u: params[:u], relo: params[:relo], ed: params[:ed], maj: params[:maj]
If you want to ignore things that aren't specified then use slice:
params.to_unsafe_h.slice(:c, :ex, :s, :sk, :lang, :cert, :u, :relo, :ed, :maj)
params.to_unsafe_h.slice(*%i[c ex s sk lang cert u relo ed maj])
Just to be safe, you might want to also remove values that there but explicitly nil:
base = params.to_unsafe_h
.slice(*%i[c ex s sk lang cert u relo ed maj])
.select { |_, v| !v.nil? }
Then when you're building your links, merge in the extras:
<% if params[:job].present? %>
<%= link_to users_path(base.merge(user_search: params[:user_search])), class: "btn btn-outline-danger" do %>
<i class="fa fa-times"></i> <%= "#{params[:job]}" %>
<% end %>
<% end %>
This also cleans up the duplication in your users_path calls by putting all that commonality into base.
You could also use params.permit(:c, :ex, ...) rather than params.to_unsafe_h but you're filtering them anyway it doesn't matter that much.
Also, are you sure you have the :user_search and :job parameters added to the right links? Seems that your first conditional would include job: params[:job] rather than user_search: params[:user_search].
Related
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();
});
});
I have obtained a list of names via SQL query (stored in #name), and I can loop through them in my .html.erb file. Is there a way to dynamically create a unique check_box_tag ID every loop? For example, can ind[1] be updated every loop to provide a unique ID for each check_box_tag?
<% #names.each_with_index do |n, index| %>
<% n.each_with_index do |value, ind| %>
<div class="checkbox">
<%= check_box_tag :ind[1], true, #tags %>
<%= label_tag :ind[1], value[1] %>
</div>
<% end %>
<% end %>
The end result is that I'd like to allow users to check boxes next to names of interest. For example, if my SQL query returns the names Alice, Bob, John, and Zach, and a user selects Bob and John, I would like to obtain an array that represents the following (as a series of Boolean 1's and 0's is fine):
{"Alice"=>"false", "Bob"=>"true", "John"=>"true", "Zach"=>"false"}
I've found a similar question, but I haven't quite managed to get it working for me: Ruby on Rails Forms: how to create a CheckBox Table (or List)
I managed to get it working with the following code:
<% #names.each_with_index do |n, index| %>
<% n.each do |value| %>
<div class="checkbox">
<%= check_box_tag "name[#{index}]", true, #tags %>
<%= label_tag "name[#{index}]", value[1] %>
</div>
<% end %>
<% end %>
I use <%= params.inspect %> on my output page to display results. Here are the parameters after selecting the second and third option:
<ActionController::Parameters {"utf8"=>"✓", "name"=>{"1"=>"true", "2"=>"true"}, "commit"=>"Save", "controller"=>"queries", "action"=>"custom_search"} permitted: false>
I'm trying to figure out how to let a user enter multiple "skills" in a text area separated by comma's and then have those skills turned into an array that feeds into a scope one at a time.
Also on my index view I'm listing the skills in the array so that when a user clicks on one it will actually remove that skill as a filter.
The issues that i'm having are
1) not sure I'm doing the array correctly the way i have it now
2) in the index view when you click on just one skill to remove, all skills are removed.
in my form
<p>
<%= label_tag(:sk, "Skills:") %>
<%= text_area_tag :sk, params[:sk], placeholder: "Enter skills", class: "form-control" %>
</p>
in my controllers index action
# filters on the users skills
if params[:sk].present?
#skills_list = params[:sk].split(",")
#skills_list = #skills_list.to_a
#skills_list.each do |skill|
#users = #users.by_skills_array(skill)
end
end
in my user model I call the scope (The skill model is separate but belongs_to the user model so that's why i'm doing the join).
scope :by_skills_array, -> (skill) { joins(:skills).distinct.where( 'skills.name LIKE ?', "%#{skill}%" ) }
in my index I'm doing
<% if params[:sk].present? %>
<% #skills_list.each do |skill| %>
<%= link_to users_path( c: params[:c], ex: params[:ex], s: params[:s], lang: params[:lang], cert: params[:cert],
u: params[:u], relo: params[:relo], ed: params[:ed], maj: params[:maj], job: params[:job] ) , class: "btn btn-default" do %>
<i class="fa fa-times"></i> <%= "#{skill}" %>
<% end %>
<% end %>
<% end %>
here's the output
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>
I am trying to create a compare functionality for an index of schools. Currently I am using the following code which takes any checked school and adds it to the school_ids[] in the params.
In my gemfile:
gem 'will_paginate'
In my school's index.html.erb:
<%= form_tag compare_path, :method => 'get' do %>
<%= submit_tag "Compare" %>
<ul>
<% #schools.each do |school| %>
<li>
<%= check_box_tag'school_ids[]', school.id %>
<%= link_to school.name, school %><br>
<%= school.city %>, <%= school.state %>
</li>
<% end %>
</ul>
<% end %>
In my school controller I have:
def compare
#schools = School.find(params[:school_ids])
end
This works great as long as all of the check schools are on the same page. But since I'm using will_paginate to paginate the list of schools, if I change pages, the check boxes do not persist. I'm assuming I need to save to sessions somehow.
Do you mean you want to be able to add a check mark to a school A on page 1 of the index, go to page 2 of the index and add another check mark for school B, then submit the compare form and see schools A and B? If that's the case, then you're correct, you need to get the check boxes into the session. Attach a js click event, like
$('.checkbox_class').click(function(){
$.post('update_session_method', { school_id: $(this).val(), checked: $(this).is(:checked)]);
});
then add a controller method
def update_session_method
session[:school_ids] ||= []
if params[:checked]
session[:school_ids] << params[:school_id]
else
session[:school_ids].delete(params[:school_id])
end
end
then your compare method
def compare
#schools = School.find(params[:school_ids].merge(session[:school_ids] || []))
end