Search method Ruby - ruby-on-rails

I made this method for a search method in ruby with a form.
I think, that this code can be simplify. What do u think ?
And I have an issue. When a user does not enter anything in the form, the results display all the tips in the database.
But when a user combines two searches, for example a keywords and a country, the result is all the tips that have the keywords as well as all the tips that have the country concerned. Except what I'm looking for is for the two criteria to be combined.
Search.rb
require 'pry'
class Search < ApplicationRecord
def self.search(keywords, category_id,city,country)
table_of_ids_country = []
table_of_ids_city = []
table_of_ids_title = []
table_of_ids_cats = []
two_searches_ids = []
merged_table_of_tips_ids = []
#results = []
tips_by_country = Tip.where(["country like?",country]) if country.present?
tips_by_city = Tip.where(["city like?",city]) if city.present?
tips_by_keyword = Tip.where(["title LIKE?","%#{keywords}%"]) if keywords.present?
tips_by_cat = Category.where(["id = :id",{id:category_id}]) if category_id.present?
two_searches = Tip.where(["title LIKE?",keywords]) if keywords.present? && Category.where(["id = :id",{id:category_id}]) if category_id.present?
if tips_by_country != nil
tips_by_country.each do |tip|
tip.id
table_of_ids_country << tip.id
end
end
if tips_by_city != nil
tips_by_city.each do |tip|
tip.id
table_of_ids_city << tip.id
end
end
if tips_by_keyword != nil
tips_by_keyword.each do |tip|
tip.id
table_of_ids_title << tip.id
end
end
if two_searches != nil
two_searches.each do |tip|
tip.id
two_searches_ids << tip.id
end
end
if tips_by_cat != nil
Category.find(tips_by_cat[0].id).tips.each do |tip|
table_of_ids_cats << tip.id
end
end
merged_table_of_tips_ids = [table_of_ids_title, table_of_ids_cats,table_of_ids_city,table_of_ids_country,two_searches_ids].flatten
merged_table_of_uniq_tips_ids = merged_table_of_tips_ids.uniq
merged_table_of_uniq_tips_ids.each do |tip|
result = Tip.find(tip)
#results << result
binding.pry
end
return #results
end
end
searches_controller
require 'pry'
class SearchesController < ApplicationController
def new
#search = Search.new
end
def create
#search = Search.create(search_params)
redirect_to #search
end
def show
#search = Search.find(params[:id])
#search = Search.search(#search.keywords, #search.category_id,#search.city,#search.country)
#Permet d'envoyer les paramètres au model search et à les réutilisé dans la méthode self.search
end
private
def search_params
params.require(:search).permit(:keywords,:category_id,:id,:city,:country)
end
end
my form :
<%=form_for #search do |f| %>
<div class="form-group">
<%= f.label :keywords, "Mots-clés" %>
<%= f.text_field :keywords, class: "form-control", placeholder: "plongée, randonnée, temple..." %>
</div>
<div class="form-group">
<%= f.label :city, "Mots-clés" %>
<%= f.text_field :city, class: "form-control", placeholder: "plongée, randonnée, temple..." %>
</div>
<div class="form-group">
<%= f.label :country, "Mots-clés" %>
<%= f.text_field :country, class: "form-control", placeholder: "plongée, randonnée, temple..." %>
</div>
<div class="form-group">
<%= f.label :category_id, "Catégories" %><br>
<%= f.collection_select :category_id, Category.all, :id, :name, :include_blank => true %>
</div>
<div class="form-group d-flex justify-content-center">
<%= f.submit "Rechercher", class: "btn btn-primary" %>
</div>
<%end%>

class Search < ApplicationRecord
def self.search(keywords, category_id,city,country)
ids = if keywords.present? && category_id.present?
Tip.keywords(keywords).pluck(:id) & Category.category_id(category_id).pluck(:id)
elsif [keywords, category_id, city, country].any?(&:present?)
Tip.country(country)
.city(city)
.keywords(keywords)
.pluck(:id)
else
[]
end
Tip.find(ids.uniq)
end
end
class Category < ApplicationRecofd
scope :category_id, ->(category_id) {
where(["id = :id",{id:category_id}])
}
end
class Tip < ApplicationRecord
scope :country, ->(country) {
where("country like?",country) if country.present?
}
scope :city, ->(city) {
where("city like?",city) if city.present?
}
scope :keyword, ->(keywords) {
tips = tips.where("title LIKE?","%#{keywords}%") if keywords.present?
}
end
also notice I'm doing a union on category ids, so keep in mind
2.5.1 :002 > [1,2] && [2,3]
=> [2, 3]
2.5.1 :003 > [1,2] & [2,3]
=> [2]

Related

How to forward model validation errors to a view based on another model in Rails?

I created a data import page to centralize the feature, and remove numerous import controllers.
I have a DataImport object to grab parameters for the DataImports controller.
The controller identifies the required importation model, and kicks the importation script. If it is successfull, it renders imported objects index, if not, it comes back to the data import form.
As you can see, in case of validation failure, the save method from the organisation_import.rb model returns the error messages list. How can I pass this list of errors to the #data_import instance so that when rendering back the new view, errors are listed ?
Thanks for your help!
Model: data_import.rb
class DataImport
include ActiveModel::Model
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_accessor :file, :playground_id, :object_type_id
def initialize(attributes = {})
attributes.each { |name, value| send("#{name}=", value) }
end
def persisted?
false
end
end
View: new.html.erb
<% provide(:title, (t('DataImport'))) %>
<% provide :page_heading do %>
<%= t('DataImport') %>
<% end %>
<%= render partial: "shared/error_list", locals: { errors: #data_import.errors } %>
<div class="row mat-form-row">
<div class="col-md-12"> <h3><%= t('DataImport') %></h3>
</div>
</div>
<%= form_for #data_import, html: {id: "input_form"} do |f| %>
<div class="row mat-form-row">
<div class="mat-form-field col-md-3">
<%= f.label :object_type, t('ObjectType'), class: "mat-form-field-label" %>
<%= f.collection_select :object_type_id, qdm_object_types('import'), :id, :name, {}, { class: "mat-input-element" } %>
</div>
</div>
<div class="row mat-form-row">
<div class="mat-form-field col-md-3">
<%= f.label :file, t('SourceFile'), class: "mat-form-field-label" %>
<%= f.file_field :file, class: "mat-input-element" %>
</div>
</div>
<br/>
<div class="row">
<div class="mat-form-field col-md-3">
<%= f.label :playground, t('Playground'), class: "mat-form-field-label" %>
<%= f.collection_select :playground_id, list_of_playgrounds, :id, :translation, {}, { class: "mat-input-element" } %>
</div>
</div>
<div class="mat-button-group">
<%= link_to t('Cancel'), root_path, method: :get, class: "mat-stroked-button mat-button-base" %>
<%= submit_tag(t('Submit'), :onclick => "submitform();", class: "mat-flat-button mat-button-base mat-primary" ) %>
</div>
<% end %>
Controller: data_imports_controller.rb
class DataImportsController < ApplicationController
# Check for active session
before_action :authenticate_user!
def new
#data_import = DataImport.new
end
def create
#data_import = DataImport.new(params[:data_import])
if #data_import.file.nil?
render :new, notice: t('FileNameCannotBeEmpty')
end
objects = Parameter.find(#data_import.object_type_id).name.downcase.pluralize
objects_import = "#{objects}Import".classify.constantize
#imported = objects_import.new(file: #data_import.file, playground: #data_import.playground_id)
if #imported.save
redirect_to "/#{objects}", notice: t('ImportedObjects')
else
render :new
end
end
end
Example of organisations_import.rb model:
class OrganisationsImport
include ActiveModel::Model
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_accessor :file, :playground
def initialize(attributes = {})
attributes.each { |name, value| send("#{name}=", value) }
end
def persisted?
false
end
def save
if imported_organisations.map(&:valid?).all?
imported_organisations.each(&:save!)
true
else
imported_organisations.each_with_index do |column, index|
column.errors.full_messages.each do |message|
errors.add :base, "Row #{index+2}: #{message}"
end
end
false
end
end
def imported_organisations
#imported_organisations ||= load_imported_organisations
end
def load_imported_organisations
spreadsheet = self.open_spreadsheet(file)
spreadsheet.default_sheet = 'Organisations'
header = spreadsheet.row(1)
(2..spreadsheet.last_row).map do |i|
record = Organisation.new
record.playground_id = $Unicity ? 0 : playground # If only one tenant, then the organisations belong to Governance
record.code = spreadsheet.cell(i,1).to_s
record.name = spreadsheet.cell(i,6)
record.parent_code = spreadsheet.cell(i,2).to_s # before_save action sets the parent_id
record.organisation_level = spreadsheet.cell(i,3) # overwriten by the before_save action
record.created_by = 'admin'
record.updated_by = 'admin'
record.owner_id = 1 # Administrator
record.status_id = Parameter.find_by_name("New").id if Parameter.exists?(:name => ("New"))
# Add description translations
next_cell = 6
ApplicationController.helpers.list_of_languages.order(:property).each do |translation|
record.description_translations.build(field_name: 'name', language: translation.property, translation: spreadsheet.cell(i,next_cell))
next_cell += 1
record.description_translations.build(field_name: 'description', language: translation.property, translation: spreadsheet.cell(i,next_cell))
next_cell += 1
end
puts "####################### test ####################################"
puts record.attributes
record
end
end
def open_spreadsheet(file)
case File.extname(file.original_filename)
when ".csv" then Roo::CSV.new(file.path, csv_options: {col_sep: ";"})
# when ".xls" then Roo::Excel.new(file.path, nil, :ignore)
when ".xlsx" then Roo::Excelx.new(file.path)
else raise "Unknown file type: #{file.original_filename}"
end
end
end
You can append error messages to your #data_import object's errors.
#imported.errors.full_messages.each{|msg| #data_import.errors[:base] << msg}

Rails 5: Add options for select if check_box equals true

I am trying to display different options for users to select in the show.html.erb, when the provider chooses to offer sets.
The problem is that the provider has multiple options, 1 bottle, 3 bottles, 6 and 12 bottles.
My _form.html.erb abstract:
<% if #wine.is_1 == true %>
<%= f.select :bottles, [["1 bottle", "1"]], id: "bottle", prompt: "Select...", class:"form-control" %>
<% end %>
<% if #wine.is_3 == true %>
<%= f.select :bottles, [["3 bottles", "3"]], id: "bottle", prompt: "Select...", class:"form-control" %>
<% end %>
<% if #wine.is_6 == true %>
<%= f.select :bottles, [["6 bottles", "6"]], id: "bottle", prompt: "Select...", class:"form-control" %>
<% end %>
Is there any alternative using the Reservations Controller to make the code minimal ? And how would I get the total amount to pay ?
Reservations Controller
class ReservationsController < ApplicationController
before_action :authenticate_user!
def create
wine = Wine.find(params[:wine_id])
if current_user == wine.user
flash[:alert] = "You cannot book your own wine!"
else
start_date = Date.parse(reservation_params[:start_date])
#reservation = current_user.reservations.build(reservation_params)
#reservation.wine = wine
#reservation.price = wine.price
#reservation.total = wine.price * #bottles
#reservation.save
flash[:notice] = "Booked Successfully!"
end
redirect_to wine
end
you could use some helpers to avoid all that logic in the view. An example:
module ApplicationHelper
def wine_quantity(wine)
case
when wine.is_1 then [["1 bottle", "1"]]
when wine.is_3 then [["3 bottles", "3"]]
when wine.is_6 then [["6 bottles", "6"]]
when wine.is_12 then [["12 bottles", "12"]]
else
end
end
end
and in your _form.html.erb it could be reduced to this:
<%= f.select :bottles, wine_quantity(#wine), id: "bottle", prompt: "Select...", class:"form-control" %>

Query always the same with Sunspot/Solr on rails

I have a problem with sunspot. When i do a SOLR request i have always the same query.
SOLR Request (3.7ms) [ path=select parameters={fq: ["type:Tag"], start: 0, rows: 30, q: "*:*"} ]
No matter what I type in the search field , it's always the same query
Here my code :
tag.rb
class Tag < ActiveRecord::Base
belongs_to :pictogram
searchable do
text :name
end
end
searchs_controller.rb
class SearchsController < ApplicationController
def index
if params[:search]
pictograms = Array.new
#term = params[:query]
search = Tag.search do
fulltext #term
end
index = 0
search.results.each do |t|
if !pictograms.include? t.pictogram
pictograms[index] = t.pictogram
index = index + 1
end
end
#results = pictograms.group_by { |p| p.category }
#search_mode = true
end
end
end
index.html
<%= form_tag result_search_path do %>
<div class="input-group input-group-lg">
<%= search_field_tag :term,"#{#term}", class: 'form-control' %>
<span class="input-group-btn">
<%= submit_tag "Search", :class => 'btn btn-primary btn' %>
</span>
</div>
<% end %>
I think the params you are using for search are not correct.
<%= search_field_tag :term,"#{#term}", class: 'form-control' %>
means that the params that you are passing back to the controller is [:term],
so the code should be something like:
if params[:term].present?
pictograms = Array.new
#term = params[:term]
search = Tag.search do
fulltext params[:term]
end
and not params[:search] and params[:query] like you have tried.

Validation in form?

I am trying to do validation but didnot find the right way while uploading the excel
Code for view
<%= form_for :dump, :url=> import_csv_index_path, :html => { :multipart => true } do |f| %>
<% if (-----).errors.any? %>
<div id="error_explanation">
<%= pluralize((----).errors.count, "error") %> prohibited this link from being saved:
<% (---).errors.full_messages.each do |msg| %>
<li><%= msg %></li>
</div>
<% end %>
<%= f.label :'Select an Excel File:' %>
<%= f.file_field :file %>
<i href="#" id="blob_1" class="icon-info-sign" rel="popover" style="margin-top: 300px"></i><br/>
<%= submit_tag 'Submit' ,:id => 'submit',:class =>"btn btn-primary"%><br/><br/>
<% end -%>
Code for model:
class Person < ActiveRecord::Base
attr_accessible :date, :email, :name, :avatar ,:template_id
validates_presence_of :email,:message => 'Email is compulsory'
end
What do i write at place of (-----)
Thank you
I have done with the other approach so discuss here,
Code for controller:
def import
#error = []
#error = Person.import(params[:dump][:file])
redirect_to people_path, notice: "Employees imported.",:flash => { :error => "Employees( Email : invalid/blank) are not saved : #{#error.join(" , ")}" }
end
In model:
def self.import(file)
spreadsheet = open_spreadsheet(file)
#err = []
#alt = []
header = spreadsheet.row(1)
(2..spreadsheet.last_row).each do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
#person = Person.find_by_id(row["id"]) ||Person.new
#person.name = row["Name"].to_s
#person.date = row["Date"]
#person.email = row["Email"]
if #person.save
else
#err << {:name => row["Name"].to_s,:error => #person.errors.messages}
end
end
#err.each do |t|
#alt.push(t[:name])
end
return #alt
end
and call that as flash message...and working fine now.
Thanks
I think somewhere in your controller you have to call .validate on a new instance of your Person model with your form's data. You can save that model in an instance variable that you can put in place of the (-----).

How to add extra attribute in model to move calculation out of view?

For the following RubyOnRails code, is there a way to move the "profit" calculation out of the View and into the Model.. so there's maybe an attribute called total_income and total_expense?
Model - transaction.rb
class Transaction < ActiveRecord::Base
attr_accessible :name, :amount, :category
scope :incomes, :conditions => { :category => 'Income' }
scope :expenses, :conditions => { :category => 'Expense' }
end
Controller - transactions_controller.rb
class TransactionsController < ApplicationController
def index
#incomes = Transaction.incomes
#expenses = Transaction.expenses
#transaction = Transaction.new
end
View - index.html.erb
<pre>
<strong>Income</strong>
<% #incomes.each do |income| %>
<%= income.name %> - <%= number_to_currency((income.amount.nil? ? 0 : income.amount)) %>
<% end %>
<strong>Subtotal:</strong> <%= number_to_currency(#income_total = #incomes.sum(:amount)) %>
<strong>Expenses</strong>
<% #expenses.each do |expense| %>
<%= expense.name %> - <%= number_to_currency((expense.amount.nil? ? 0 : expense.amount)) %>
<% end %>
<strong>Subtotal:</strong> <%= number_to_currency(#expenses_total = #expenses.sum(:amount)) %>
<strong>Profit: <%= number_to_currency(#income_total - #expenses_total) %></strong>
</pre>
For the most basic change you could just add class methods to Transaction
class Transaction < ActiveRecord::Base
attr_accessible :name, :amount, :category
scope :incomes, :conditions => { :category => 'Income' }
scope :expenses, :conditions => { :category => 'Expense' }
def self.income_total
incomes.sum :amount
end
def self.expenses_total
expenses.sum :amount
end
end
class TransactionsController < ApplicationController
def index
#incomes = Transaction.incomes
#expenses = Transaction.expenses
#income_total = Transaction.income_total
#expenses_total = Transaction.expenses_total
#transaction = Transaction.new
end
These days I prefer to wrap these multiple, interdependent instance variables up into a new presenter class, something like this (untested):
class TransactionPresenter
attr_reader :income_total, :expenses_total
def initialize
#incomes = Transaction.incomes
#expenses = Transaction.expenses
end
def each_income(&block)
#incomes.each(&block)
end
def each_expense(&block)
#incomes.each(&block)
end
def income_total
#income_total ||= number_to_currency(#incomes.sum(&:amount))
end
def expenses_total
#expenses_total ||= number_to_currency(#expenses.sum(&:amount))
end
def name_and_amount(income_or_expense)
"#{income_or_expense.name} - #{number_to_currency((income.amount.nil? ? 0 : income.amount))}"
end
def profit
number_to_currency(income_total - expenses_total)
end
end
# controller
def index
#presenter = TransactionPresenter.new
end
# view
<pre>
<strong>Income</strong>
<% #presenter.each_income do |income| %>
<%= #presenter.name_and_amount %>
<% end %>
<strong>Subtotal:</strong> <%= #presenter.income_total %>
<strong>Expenses</strong>
<% #presenter.each_expense do |expense| %>
<%= #presenter.name_and_amount %>
<% end %>
<strong>Subtotal:</strong> <%= #presenter.expenses_total %>
<strong>Profit: <%= #presenter.profit %></strong>
</pre>

Resources