Rails: pass an array to a scope - ruby-on-rails

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

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();
});
});

remove empty params from url

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].

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>

Check_box_tag not displaying labels properly

Everything is posting correctly, but I do not see any labels in my checkboxes, just blanks. My form looks like this:
<%= form_for #itemrecord do |f| %>
<div class="col-xs-12">
<p><b>Items people are asking for</b></p>
</div>
<% #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 %>
What's happening is that wishlist is a hash of categories and items within those categories that I set in the controller, and is then called by multiple form builders to build checkboxes. The challenge is that right now in the current implementation, the checkboxes params are passed through properly (FYI controller code is at the bottom), but beside each checkbox, there is no text that shows the thing (i.e., no label so that people know what they're checking.
Here's the html generated for one checkbox (it's the same for all checkboxes)
Basically, I need to make the value the label.
FYI what's happening is that for every item checked, a record is being created. Here's the controller code:
def create
items_to_be_saved = []
inventory_params.each do |i|
items_to_be_saved << ({ :signup_id => Signup.find_by_email(session[:signup_email]).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(":item_name")
end
In your code:
<%= check_box_tag ":item_name[]", "#{thing}" %>
Second parameter for check_box_tag is not a label value, it is just a value which goes to controller in parameters. If you wan't to display label within your checkbox you will need to call label_tag in your view:
= label_tag ':item_name[]', thing
= check_box_tag ':item_name[]'
But you definitely should check simple_form gem which allows you to render checkboxes in much cleaner way:
f.input :field, as: :boolean, inline_label: 'Label'
can you please try it and let me know
<%= check_box_tag 'item_name[]', thing %>

group_by display belongs_to 'name' followed by has_many 'items'

I'm trying to display group name (product_group) followed by the items (product) in each group.
<% #products.group_by(&:product_group_id).each do |s|%>
<!--need to get group name here ->
<% s[1].each do |d|%>
<%= d.product_name%>
<br>
<%end%>
<%end%>
rails 2.3.8
First, I strongly recommend using two variables in your block. When you use one variable, group_by sets the variable to an array of the pair of values which should be set as two variables. It will be much more clear code than indexing the pair with [1] for the group.
One way is that the first part of the pair will be the id, so you can do a find.
<% #products.group_by(&:product_group_id).each do |product_group_id, products|%>
<!--need to get group name here -->
<%= ProductGroup.find(product_group_id).name %>
<% products.each do |product|%>
<%= product.product_name%>
<br>
<% end %>
<% end %>
Another way, is since you have an array of at least on product in the group, you can call the product_group association on the first element of the array.
<% #products.group_by(&:product_group_id).each do |product_group_id, products|%>
<!--need to get group name here -->
<%= products[0].product_group.name %>
<% products.each do |product|%>
<%= product.product_name%>
<br>
<% end %>
<% end %>
You can also delegate the name to the product group.
class Product
belongs_to :product_group
delegate :name, :to => :product_group, :prefix => true, :allow_nil => true
end
<%= products[0].product_group_name %>
Group by needs two variables in the block declaration. The first for the thing you are grouping by, and the second to hold the things in each of the groups.
<% #products.group_by(&:product_group_id).each do |group_id, group_products| %>
Group ID: <%= group_id %>
<br>
Products:
<% group_products.each do |product| %>
<%= product.product_name %>
<% end %>
<% end %>

Resources