Iterating through an lazy load array - ruby-on-rails

I'm tryin to build an array of hashes and then iterate through.
If I use the code below
#elements = [{:profile_id=>"123"}, {:profile_id=>"456"}]
#categories = Array.new
#elements.each do |element|
category = Category.find_by_profile_id(element[:profile_id])
#categories.push(category)
end
It will find the last object and it will add to the array. However I would like to fetch all the records with that id.
I have been trying the code bellow
#elements = [{:profile_id=>"123"}, {:profile_id=>"456"}]
#categories = Array.new
#elements.each do |element|
category = RootCategory.where(profile_id: element[:profile_id])
#categories.push(category)
end
#response
[#<ActiveRecord::Relation [#<Category id: "123", ...">, #<Category id: "123", ..."> #<ActiveRecord::Relation [#<Category id: "456", ...">, #<Category id: "456", ...">]>]
I can't iterate through the returned array. If I try
#categories.each do |t|
puts t.name
end
#Prints
#Category
#Category

This is mostly because you are abusing each for mapping. ActiveRecord::Base#where returns a Relation object (without a loss of generality it might be considered an array here). What you need is Enumerable#flat_map:
elements = [{:profile_id=>"123"}, {:profile_id=>"456"}]
#categories =
elements.flat_map do |element|
RootCategory.where(profile_id: element[:profile_id])
end
or, even better, avoid multiple queries, since where happily accepts an array:
#categories =
RootCategory.where(profile_id: elements.map { |e| e[:profile_id] })
The latter is basically executing:
RootCategory.where(profile_id: [123, 456])
retrieving categories for all the profiles specified.

Related

Move records from an array of records if matched id

Using Rails 6.
My dirty code
active_card_ids = JSON.parse(cookies[:card_ids])
#active_cards = []
#best_cards = Houses.where("#{payment_category} IS NOT NULL").order("#{payment_category} DESC")
#best_cards.each do |card|
if active_card_ids.include? card.id
#active_cards << card
end
end
What I am trying to do
I have an array of records queried: queried_records = [<object id: 1>, <object id: 3>, <object id: 4>, <object id: 7>]
Another array of IDs: wanted_ids = [4, 5]
Create another array: selected_records = []
I want to move queried_records found in wanted_ids, to selected_records.
In this example, we found <object id: 4> from queried_records, so we want to move (not copy) it to selected_records.
The results should be selected_records return an array [<object id: 4>]
If I understood your problem correctly, there will be id's in active_card_ids and you want another array with those id's only.
You can try querying the DB directly, then you don't have to run the loop.
The below sample takes an array of id's and passes it to where IN clause to query only active cards.
active_card_ids = JSON.parse(cookies[:card_ids])
#active_cards = Houses.where(id: active_card_ids).where("#{payment_category} IS NOT NULL").order("#{payment_category} DESC")
#best_cards = Houses.where("#{payment_category} IS NOT NULL").order("#{payment_category} DESC")
Another approach you can try is:
active_card_ids = JSON.parse(cookies[:card_ids])
#best_cards = Houses.where("#{payment_category} IS NOT NULL").order("#{payment_category} DESC")
#active_cards = #best_cards.select{ |card| active_card_ids.include? card.id }

How to push/shovel/append active record relation correctly?

I have a fairly complex search form that allows for multiple duplicate nested fields to be submitted at once. I generate unique id's on clone so i can separate them using jquery. I then iterate over the duplicates and do something like the following:
So i have something like:
{..., "search"=>{"searches"=>{}, "searches_0"=>{}...{other_attributes}}
def search_post
search = []
params.each do |k, v|
search << do_search(params, v)
end
end
def do_search(search, v)
search_array = []
search = Model.where() if v[:this_param].present?
search = Model.where() if v[:that_param].present?
# it will be only one of the `search` as this_param or that_param can't be searched
together
search_array << search.where(attr: search[:attr]) if attr.present?
search_array << search.where(attr_2: search[:attr_2]) if attr_2.present?
search_array << search.where(attr_3 search[:attr_3]) if attr_3.present?
search_array.uniq
end
This gives a result like:
[#<ActiveRecord::Relation [#<LineItem id: 15, created_at: "2020-01-03 15:49:19", updated_at: "2020-01-03 15:49:19", ...>]>, #<ActiveRecord::Relation [#<LineItem id: 14, created_at: "2020-01-03 15:49:19", updated_at: "2020-01-03 15:49:19", ...>]>]
I obviously get an array but I need to do more queries on it.
I have tried using search.reduce([], :concat).uniq but this only removes all of the results and only keeps the ActiveRecord::Relation aspect of the array.
What I need is to shovel the results from the loop and be able to use where on it.
How can this be accomplished?
By the looks of it you can try using a query object:
class ModelQuery
def initialize(initial_scope = Model.all)
#initial_scope = initial_scope
end
def call(params)
scoped = by_this_param(initial_scope, params[:this_param])
scoped = by_that_param(initial_scope, params[:that_param])
scoped = by_other_param(initial_scope, params[:other])
# ...
scoped
end
def by_this_param(s, this_param = nil)
this_param ? s.where(this_attr: this_param) : s
end
def by_that_param(s, that_param = nil)
that_param ? s.where(that_attr: that_param) : s
end
# ...
def by_other(s, other = nil)
other ? s.where(other_attr: other) : s
end
end
You can then do things like:
q = ModelQuery.new
# or perhaps...
# q = ModelQuery.new(Model.where(whatever: "however"))
q.call params
q.order(whatever_column: :asc).group(:however)
Obviously you need to adapt and extend the code above to your variable names/parameters. Another upside of using the query object pattern is that it gives you a subtle nudge to structure your parameters coherently so you can pass from view and return from AR quickly, without much fiddling about with the input/output.
Give it a try!

Ruby - Extract value of a particular key from array of hashes

I have an array of hashes - #profiles which has data as:
[{:user_id=>5, :full_name=>"Emily Spot"},{:user_id=>7, :full_name=>"Kevin Walls"}]
I want to get full_name of say user_id = 7? I'm doing the following: but it's throwing an error that expression #profiles.find{|h| h[':user_id'] == current_user.id} is nil.
name = #profiles.find{ |h| h[':user_id'] == current_user.id }[':full_name']
if I use select instead of find then error is - no implicit conversion of String into Integer.
How do I search through the array of hashes?
UPDATE:
After #Eric's answer, I restructured my job model & view actions:
def full_names
profile_arr||= []
profile_arr = self.applications.pluck(:user_id)
#profiles = Profile.where(:user_id => profile_arr).select([:user_id, :first_name, :last_name]).map {|e| {user_id: e.user_id, full_name: e.full_name} }
#full_names = #profiles.each_with_object({}) do |profile, names|
names[profile[:user_id]] = profile[:full_name]
end
end
In the view....,
p #current_job.full_names[current_user.id]
#profiles is an array of hashes, with symbols as keys, whereas what you use is String objects.
So ':user_id' is a string, and you want symbol: :user_id:
#profiles.find{ |h| h[:user_id] == current_user.id }
I want to get full_name of say user_id == 7
#profiles.find { |hash| hash[:user_id] == 7 }.fetch(:full_name, nil)
Note, I used Hash#fetch for case, when there is no hash with value 7 at key :user_id.
As you've noticed, it's not very convenient to extract the name of user_id 7. You could modify your data structure a bit :
#profiles = [{:user_id=>5, :full_name=>"Emily Spot"},
{:user_id=>7, :full_name=>"Kevin Walls"}]
#full_names = #profiles.each_with_object({}) do |profile, names|
names[profile[:user_id]] = profile[:full_name]
end
p #full_names
# {5=>"Emily Spot", 7=>"Kevin Walls"}
p #full_names[7]
# "Kevin Walls"
p #full_names[6]
# nil
You didn't lose any information but name look-up is now much faster, easier and more robust.
Suggesting, to create a new hash that can make things simpler
Eg:
results = {}
profiles = [
{user_id: 5, full_name: "Emily Spot"},
{user_id: 7, full_name: "Kevin Walls"}
]
profiles.each do |details|
results[details[:user_id]] = details[:full_name]
end
Now, results will have:
{5: "Emily Spot", 7: "Kevin Walls"}
So, if you need to get full_name of say user_id = 7, simply do:
results[7] # will give "Kevin Walls"

Rails: each for multiple and one object data

I'm new in rails and need to clear one question:
for example my method return such data:
#<Article ART_ID: 1151754, ART_ARTICLE_NR: "0 281 002 757", ART_SUP_ID: 30, ART_DES_ID: nil, ART_COMPLETE_DES_ID: 62395, ART_CTM: nil, ART_PACK_SELFSERVICE: 0, ART_MATERIAL_MARK: 0, ART_REPLACEMENT: 0, ART_ACCESSORY: 0, ART_BATCH_SIZE1: nil, ART_BATCH_SIZE2: nil, datetime_of_update: "2012-09-25 17:49:18">
or array, not only one object: how could use each func then?
for example:
articles = ArtLookup.search_strong_any_kind_without_brand(params[:article_nr].gsub(/[^0-9A-Za-z]/, ''))
binding.pry
if articles.present?
articles.each do |a|
#all_parts_result <<
{
analogue_manufacturer_name: a.supplier.SUP_BRAND,
analogue_code: a.ART_ARTICLE_NR,
delivery_time_min: '',
delivery_time_max: '',
min_quantity: '',
product_name: a.art_name,
quantity: '',
price: '',
distributor_id: '',
link_to_tecdoc: a.ART_ID
}
end
end
now i get errors like
`undefined method `each' for `#<Article:0x007f6554701640>
i think it is becouse i have sometimes one object, sometimes 10, and sometime 0.
how is it beatifull and right to do in rails?
Your search_strong_any_kind_without_brand method is looping through your articles based on the search condition. If the article matches then you are setting #art_concret to the match and then returning the match. However, you're not finding all matches, just the last one.
.
loop
#art_concret = art
end
.
return #art_concret
If you set the #art_concret as an array and inject results into this instance variable, then you will have the resulting search in array form. However, keep in mind that this does kind of break the ActiveRecord ORM as you would be returning a simple array and not an ActiveRecord Relation array.
def self.search_strong_any_kind_without_brand(search)
search_condition = search.upcase
#art_concret = []
#search = find(:all, :conditions => ['MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE)', search_condition])
#articles = Article.find(:all, :conditions => ["ART_ID in (?)", #search.map(&:ARL_ART_ID)])
#binding.pry
#articles.each do |art|
if art.ART_ARTICLE_NR.gsub(/[^0-9A-Za-z]/, '') == search
#art_concret << art
end
end
return #art_concret
end
If you want to keep the code a bit cleaner then use select on your matching condition instead of looping through each article in #articles.
def self.search_strong_any_kind_without_brand(search)
search_condition = search.upcase
#search = find(:all, :conditions => ['MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE)', search_condition])
#articles = Article.find(:all, :conditions => ["ART_ID in (?)", #search.map(&:ARL_ART_ID)])
#binding.pry
return #articles.select { |art| art.ART_ARTICLE_NR.gsub(/[^0-9A-Za-z]/, '') == search }
end
Unrelated: is there a reason why you're using instance variables in search_strong_any_kind_without_brand?
I think the right thing to do is to make sure your method always returns an array (or enumerable).
looking at the code you posted in to pastebin I would recommend you use Array#select in your method
for example you might be able to just return this:
#articles.select { |art| art.ART_ARTICLE_NR.gsub(/[^0-9A-Za-z]/, '') == search }
assuming #articles is an array or collection you will always get an array back, even if it is 0, or 1 element
This answer would be a bit offtopic, but I would like to mention a splat operator:
[*val]
will produce array, consisting of either single val value whether it’s not an array, or the array itself whether val is an array:
▶ def array_or_single param
▷ [*param].reduce &:+ # HERE WE GO
▷ end
=> :array_or_single
▶ array_or_single [1,2,3]
=> 6
▶ array_or_single 5
=> 5
That said, you code would work with this tiny improvement:
- articles.each do |a|
+ [*articles].each do |a|
Hope it gives a hint on how one might handle the data, coming from the 3rd party. As an answer to your particular question, please follow the advises in the other answers here.

Ruby On Rails : create nested Array

I'm trying to make an array of objects, let's call it "categories", and I want each object in this array to have an array called "items" within it, so the result will be something like this:
[category:id=11, name="beer", items[1,2,3,4]]
I've tried this code:
#category ||= Array.new
#categoryItems ||= Array.new
#venues.categories.enabled.each do |category|
#category.push(category)
#categoryItems.push(category.items.enabled)
end
but I don't know how to name the items inside so I can use them in json afterward. How can I do this?
You can try create hash.
#category ||= Array.new
#venues.categories.enabled.each do |category|
hash = {}
hash[:category][:id] = category.id
hash[:category][:name] = category.name
hash[:category][:items] = category.items.enabled.pluck(:id).join(',')
#category << hash
end

Resources