Mongoid query - Refine Search Implementation - ruby-on-rails

I am working on refine search in Rails4 and Mongoid. This is my search params and form,
I built the query piece by piece:
if params[:procedure_id]
#blood_banks = BloodBank.where(:procedure_id => params[:procedure_id])
end
if params[:location_ids]
#blood_banks = BloodBank.where(:location_id.in => params[:location_ids])
end
...
if condition
end
If i need to search Procedure "Discectomy" in location "Hebbal" then i nned to define the if condition as follows,
if params[:procedure_id].present? && params[:location_id].present?
...
end
Then i need to do for all such combinations(4x3x2x1) of search, for my refine search!!!
Which is the best way to implement the same.
How do you achieve above situation???
I can't right all the possible if conditions, is their any shortcut method!!!
How to achieve below code:
if params[:fees].present?
if params[:fees] == "300"
#doctor_clinic = DoctorClinic.where( :consultation_fees => { '$lte' => params[:fees] }).map(&:doctor_id)
#doctors = Doctor.where(
{ :id.in => #doctor_clinic })
end
if params[:fees] == "500"
#doctor_clinic = DoctorClinic.where( :consultation_fees => { '$gte' => 300, '$lte' => params[:fees] }).map(&:doctor_id)
#doctors = Doctor.where(
{ :id.in => #doctor_clinic })
end
if params[:fees] == "1000"
#doctor_clinic = DoctorClinic.where( :consultation_fees => { '$gte' => 500, '$lte' => params[:fees] }).map(&:doctor_id)
#doctors = Doctor.where(
{ :id.in => #doctor_clinic })
end
if params[:fees] == "1001"
#doctor_clinic = DoctorClinic.where( :consultation_fees => { '$gte' => params[:fees] }).map(&:doctor_id)
#doctors = Doctor.where(
{ :id.in => #doctor_clinic })
end
end

You can do this:
conditions = {}
conditions.merge!(:procedure_id => params[:procedure_id]) if params[:procedure_id]
conditions.merge!(:location_id.in => params[:location_ids]) if params[:location_ids]
...
#blood_banks = BloodBank.where(conditions)
EDIT:
Regarding your last code, since you have no logic here (common idea for all values), you can use case:
condition = case params[:fees]
when "300"
{ '$lte' => 300 }
when "500"
{ '$gte' => 300, '$lte' => 500 }
when "1000"
{ '$gte' => 500, '$lte' => 1000 }
when "1001"
{ '$gte' => 1001 }
else
nil
end
if condition
#doctor_clinic = DoctorClinic.where( :consultation_fees => condition).map(&:doctor_id)
#doctors = Doctor.where({ :id.in => #doctor_clinic })
end

Related

Merge two hashes in ruby

I have two collections of hashes
and_filters = [{:filter=>:brand, :value=>"Fila"}, {:filter=>:brand, :value=>"Adidas"}]
or_filters = [{:filter=>:gender, :value=>"Hombre"}]
and i need make like the following struct
:_or => [
{ :_and => [
{:gender => "Hombre"},
{:brand => "Adidas"}]
},
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Fila"}]
}
]
For this i did
query[:_or] = []
or_filters.each do |or_f|
query[:_or] << {
:_and => [
and_filters.map do |and_f|
{and_f[:filter] => and_f[:value]}
end
{ or_f[:filter] => or_f[:value] }
]
}
end
but an error Expected: { shows in code. Apparently the second loop is badly syntactically
It's not pretty, but I believe this gives the desired results:
{_or: or_filters.each_with_object([]) do |or_filter, or_filter_ary|
or_filter_hsh = {or_filter[:filter] => or_filter[:value]}
and_filters.each do |and_filter|
and_filter_hsh = {and_filter[:filter] => and_filter[:value]}
or_filter_ary << {_and: [or_filter_hsh, and_filter_hsh]}
end
end
}
Which gives:
{:_or => [
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Fila"}
]},
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Adidas"}
]}
]}
It looks like you want every combination of the given and_filters with the given or_filters. In that case, and assuming you don't care about order (:gender before :brand vs. the other way around) Array#product is your friend:
result = {
_or: and_filters.product(or_filters).map do |a|
{ _and: a.map {|filter:, value:| { filter => value }} }
end
}
# => {
# :_or => [
# {:_and => [{:brand=>"Fila"}, {:gender=>"Hombre"}]},
# {:_and => [{:brand=>"Adidas"}, {:gender => "Hombre"}]}
# ]
# }
See it in action on repl.it: https://repl.it/#jrunning/HorizontalDirectCharmap
Thats what i was looking for
query = {}
query[:_or] = or_filters.map do |or_f|
and_filters_aux = and_filters.dup
and_filters_aux << or_f
{ :_and => and_filters_aux.map{|hsh| {hsh[:filter] => hsh[:value]} } }
end
https://repl.it/repls/ShyLateClients

Rails merge multiple params together

How can I merge two params together from my permissions hash that share the same "school_id" and "plan_type'. Then delete the permission that was merged from the hash, just leaving one. There can also be more than two that match.
[{"school_id"=>"1",
"plan_type"=>"All",
"view"=>"true",
"create"=>"true",
"approve"=>"true",
"grant"=>"true",
"region_id"=>nil},
{"school_id"=>"1", "plan_type"=>"All", "edit"=>"true", "region_id"=>nil},
{"school_id"=>"2",
"plan_type"=>"All",
"edit"=>"true",
"grant"=>"true",
"region_id"=>nil}]
def create_permissions(user, params)
permissions = params[:permissions].values.map { |perm|
if perm[:plan_type] == "" || perm[:plan_type] == "All Plans"
perm[:plan_type] = "All"
end
#perm_type = get_permission_type(perm)
case
when 'school' then perm.merge(region_id: nil)
when 'region' then perm.merge(school_id: nil)
end
}.tap { |permissions|
new_permissions = []
permissions.each do |perm|
set_permissions = permissions.find {|x| (x != perm && x[:school_id] == perm[:school_id] && x[:plan_type] == perm[:plan_type]) }
end
params[:user][:region_ids] = permissions.map { |perm| perm[:region_id] }.compact
params[:user][:school_ids] = permissions.map { |perm| perm[:school_id] }.compact
}
end
Output:
[{"school_id"=>"1",
"plan_type"=>"All",
"view"=>"true",
"create"=>"true",
"approve"=>"true",
"grant"=>"true",
"region_id"=>nil},
"edit"=>"true"
{"school_id"=>"2",
"plan_type"=>"All",
"edit"=>"true",
"grant"=>"true",
"region_id"=>nil}]
Group by school_id and then reduce by merging hashes:
input.group_by { |e| e['school_id'] }
.values
.map { |v| p v.reduce(&:merge) }
To group by many fields, one might use an array of desired fields, a concatenated string, whatever:
input.group_by { |e| [e['school_id'], e['plan_type']] }
.values
.map { |v| p v.reduce(&:merge) }
or, to keep nifty captions:
input.group_by { |e| "School: #{e['school_id']}, Plan: #{e['plan_type']}" }
.map { |k,v| [k, v.reduce(&:merge)] }
.to_h
#⇒ {
# "School: 1, Plan: All" => {
# "approve" => "true",
# "create" => "true",
# "edit" => "true",
# "grant" => "true",
# "plan_type" => "All",
# "region_id" => nil,
# "school_id" => "1",
# "view" => "true"
# },
# "School: 2, Plan: All" => {
# "edit" => "true",
# "grant" => "true",
# "plan_type" => "All",
# "region_id" => nil,
# "school_id" => "2"
# }
#}
arr1 = arr.group_by { |e| [e["school_id"],e["plan_type"]] }.values
=> {["1", "All"]=>[{"school_id"=>"1", "plan_type"=>"All", "view"=>"true", "create"=>"true", "approve"=>"true", "grant"=>"true", "region_id"=>nil}, {"school_id"=>"1", "plan_type"=>"All", "edit"=>"true", "region_id"=>nil}], ["2", "All"]=>[{"school_id"=>"2", "plan_type"=>"All", "edit"=>"true", "grant"=>"true", "region_id"=>nil}]}
arr1.map{ |i| i.inject({}) { |sum, e| sum.merge e}}
=> [{"school_id"=>"1", "plan_type"=>"All", "view"=>"true", "create"=>"true", "approve"=>"true", "grant"=>"true", "region_id"=>nil, "edit"=>"true"}, {"school_id"=>"2", "plan_type"=>"All", "edit"=>"true", "grant"=>"true", "region_id"=>nil}]

How can i save the conditions in variable and use that variable in rails active record?

I am trying to save the conditions in a variable and call that in the active record query as shown below
if !key1.nil?
#condition = ":key2 => #value2, :key3 => #value3"
else
#condition = ":key4 => #value4, :key5 => #value5"
end
#result = Model.where(#condition).all
How can i do this? please help me.
UPDATE:
#condition = { "key1 = ? and key2 >= ? and key3 <= ? and id IN (?)", #value1, #value2, #value3, #id }
Use hash instead of string:
#condition = { :key2 => #value2, :key3 => #value3 }
Also, you could probably simplify the syntax of if !key1.nil? by using unless:
#condition = {}
unless key1
#condition = { :key2 => #value2, :key3 => #value3 }
else
#condition = { :key4 => #value4, :key5 => #value5 }
end

Share Variables between Methods in a Rails Controller

I have a Rails controller that has 2 methods. Both Methods use some of the same variables and I'm wondering how I can refactor this into either a method in the Model of somewhere in the controller to make them more reusable than they are now.
class ChartsController < ApplicationController
before_filter :authenticate_user!, :company_id
def service_level
latest_date = Invoice.where(:account_id => #company.accounts).maximum(:invc_date)
invoices_filter = { :invoices => { :invc_date => (latest_date - 3.months)..latest_date } }
invoices = Invoice.where({:account_id => #company.accounts}.merge(invoices_filter))
details = InvoiceDetail.joins(:type).where(:invoice_id => invoices)
freight_details = details.where(:invoice_detail_types => { :category => 'freight' })
freight_groups = freight_details.group(:family).select("family, count(distinct package_id), sum(base_charge + discount)")
vol_data = {}
spend_data = {}
#charts = {}
#charts[:service_analysis] = {
:vol_data => Hash[freight_groups.map { |row| [InvoiceDetailFamily[row.family].name, row.count.to_i] }],
:spend_data => Hash[freight_groups.map { |row| [InvoiceDetailFamily[row.family].name, row.sum.to_f] }]
}
render partial: 'service_level'
end
def weight_summary
latest_date = Invoice.where(:account_id => #company.accounts).maximum(:invc_date)
invoices_filter = { :invoices => { :invc_date => (latest_date - 3.months)..latest_date } }
invoices = Invoice.where({:account_id => #company.accounts}.merge(invoices_filter))
details = InvoiceDetail.joins(:type).where(:invoice_id => invoices)
freight_details = details.where(:invoice_detail_types => { :category => 'freight' })
packages = freight_details.joins(:package, :invoice)
vol_data = {}
spend_data = {}
packages.group(:zone).select("zone, count(distinct package_id), sum(base_charge + discount)").each do |row|
case row.zone
when '02'..'08', '002'..'008', '102'..'108', '132'..'138', '202'..'208', '242'..'248', '302'..'308'
zg = row.zone[-1]
when '09'..'17', '124'..'126', '224'..'226'
zg = 'AK/HI/PR'
else
zg = 'Import/Export'
end
vol_data[zg] = (vol_data[zg] || 0) + row.count.to_i
spend_data[zg] = (spend_data[zg] || 0) + row.sum.to_f
end
#charts = {}
#charts[:weight_analysis] = {
:vol_data => Hash[(vol_data.sort_by {|key, value| key.scan(/\d+/)[0].to_i})],
:spend_data => Hash[(spend_data.sort_by {|key, value| key.scan(/\d+/)[0].to_i})]
}
render partial: 'weight_summary'
end
end
I would suggest to use a model class method for processing data. for example
freight_details = details.where(:invoice_detail_types => { :category => 'freight' })
freight_groups = freight_details.group(:family).select("family, count(distinct package_id), sum(base_charge + discount)")
vol_data = {}
spend_data = {}
#charts = {}
#charts[:service_analysis] = {
:vol_data => Hash[freight_groups.map { |row| [InvoiceDetailFamily[row.family].name, row.count.to_i] }],
:spend_data => Hash[freight_groups.map { |row| [InvoiceDetailFamily[row.family].name, row.sum.to_f] }]
}
could be moved to a model class method that returns charts. In the same way you can refactor your second method. Any kind of business logic and data proccessing should be handled in models
Moreover I can see there are too many unused local variables are there in controller. The controller should be thin as much as possible.
Use concept like decorator
module Chart
extend self
def service_analysis(freight_groups, freight_groups)
end
end
class ChartsController < ApplicationController
#chart = Chart.service_analysis(freight_groups, freight_groups)
end
notes: do not put calculation code in view, it's slow
If you decided to keep it within the controller, then try this:
class ChartsController < ApplicationController
before_filter :authenticate_user!, :company_id
before_filter :load_data, :only => [:service_level, weight_summary]
def service_level
freight_groups = #freight_details.group(:family).select("family, count(distinct package_id), sum(base_charge + discount)")
#charts = {}
#charts[:service_analysis] = {
:vol_data => Hash[freight_groups.map { |row| [InvoiceDetailFamily[row.family].name, row.count.to_i] }],
:spend_data => Hash[freight_groups.map { |row| [InvoiceDetailFamily[row.family].name, row.sum.to_f] }]
}
render partial: 'service_level'
end
def weight_summary
packages = #freight_details.joins(:package, :invoice)
vol_data = {}
spend_data = {}
packages.group(:zone).select("zone, count(distinct package_id), sum(base_charge + discount)").each do |row|
case row.zone
when '02'..'08', '002'..'008', '102'..'108', '132'..'138', '202'..'208', '242'..'248', '302'..'308'
zg = row.zone[-1]
when '09'..'17', '124'..'126', '224'..'226'
zg = 'AK/HI/PR'
else
zg = 'Import/Export'
end
vol_data[zg] = (vol_data[zg] || 0) + row.count.to_i
spend_data[zg] = (spend_data[zg] || 0) + row.sum.to_f
end
#charts = {}
#charts[:weight_analysis] = {
:vol_data => Hash[(vol_data.sort_by {|key, value| key.scan(/\d+/)[0].to_i})],
:spend_data => Hash[(spend_data.sort_by {|key, value| key.scan(/\d+/)[0].to_i})]
}
render partial: 'weight_summary'
end
private
def load_data
latest_date = Invoice.where(:account_id => #company.accounts).maximum(:invc_date)
invoices_filter = { :invoices => { :invc_date => (latest_date - 3.months)..latest_date } }
invoices = Invoice.where({:account_id => #company.accounts}.merge(invoices_filter))
details = InvoiceDetail.joins(:type).where(:invoice_id => invoices)
#freight_details = details.where(:invoice_detail_types => { :category => 'freight' })
end
end
The instance variable #freight_details will be available in both methods. The before_filter will execute the load_data method only for these two methods.
Good Luck!

Insert values from active record into a hash

I have this:
produtos = LineItem.select('codigosku, quantity').where("cart_id = #{session[:cart_id] } ")
I need to insert the result of this select (produto variable), here:
message = Hash.new
message = {
"tem:carrinho" => {"gpa:CEP" => params[:cep],
"gpa:CNPJ" => 'doc',
"gpa:IdCampanha" => 1111,
"gpa:Produtos" => {"gpa:DadosListaProdutoCarrinhoDTO" =>
{
HERE! VALUES OF "PRODUTOS VARIABLE"
}
}
}
}
How can I do this?
Thanks in advance!
create your array:
line_items_array = line_items.map{|li| li.attributes }
Then insert the array within your hash.
like in apneadiving example, use map to create an array from the produtos data; use attributes to return all data (it is a hash) from your selected data
message = {
"tem:carrinho" => {
"gpa:CEP" => params[:cep],
"gpa:CNPJ" => 'doc',
"gpa:IdCampanha" => 1111,
"gpa:Produtos" => {
"gpa:DadosListaProdutoCarrinhoDTO" => produtos.map { |item| item.attributes }
}
}
}
or if you need to be more specific about the keys in the produtos and append it after initialization
# initialize the Produtos to nil
message = {
"tem:carrinho" => {
"gpa:CEP" => params[:cep],
"gpa:CNPJ" => 'doc',
"gpa:IdCampanha" => 1111,
"gpa:Produtos" => nil
}
}
# build an array of DadosListaProdutoCarrinhoDTO
list = produtos.map do |item|
{
"gpa:DadosListaProdutoCarrinhoDTO" => {
"codigosku" => item.codigosku,
"quantity" => item.quantity
}
}
end
# set the Produtos key to an array of DadosListaProdutoCarrinhoDTO
message["tem:carrinho"].merge!({ "gpa:Produtos" => list })

Resources