Rails: each for multiple and one object data - ruby-on-rails

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.

Related

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"

Relation passed to #or must be structurally compatible. Incompatible values: [:references]

I have two queries, I need an or between them, i.e. I want results that are returned by either the first or the second query.
First query is a simple where() which gets all available items.
#items = #items.where(available: true)
Second includes a join() and gives the current user's items.
#items =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
I tried to combine these with Rails' or() method in various forms, including:
#items =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
.or(
#items
.joins(:orders)
.where(available: true)
)
But I keep running into this error and I'm not sure how to fix it.
Relation passed to #or must be structurally compatible. Incompatible values: [:references]
There is a known issue about it on Github.
According to this comment you might want to override the structurally_incompatible_values_for_or to overcome the issue:
def structurally_incompatible_values_for_or(other)
Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
(Relation::MULTI_VALUE_METHODS - [:eager_load, :references, :extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
(Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
end
Also there is always an option to use SQL:
#items
.joins(:orders)
.where("orders.user_id = ? OR items.available = true", current_user.id)
You can write the query in this good old way to avoid error
#items = #items.joins(:orders).where("items.available = ? OR orders.user_id = ?", true, current_user.id)
Hope that helps!
Hacky workaround: do all your .joins after the .or. This hides the offending .joins from the checker. That is, convert the code in the original question to...
#items =
#items
.where(orders: { user_id: current_user.id})
.or(
#items
.where(available: true)
)
.joins(:orders) # sneaky, but works! 😈
More generally, the following two lines will both fail
A.joins(:b).where(bs: b_query).or(A.where(query)) # error! 😞
A.where(query).or(A.joins(:b).where(bs: b_query)) # error! 😞
but rearrange as follows, and you can evade the checker:
A.where(query).or(A.where(bs: b_query)).joins(:b) # works 😈
This works because all the checking happens inside the .or() method. It's blissfully unaware of shennanigans on its downstream results.
One downside of course is it doesn't read as nicely.
I ran into the same issue, however the code was defined in a different place and was very difficult to change directly.
# I can't change "p"
p = Post.where('1 = 1').distinct # this could also be a join
And I needed to add an or statement to it
p.or(Post.where('2 = 2'))
The following code won't raise an error, because it has distinct like the initial relationship.
p.or(Post.where('2 = 2').distinct)
The problem with it it that it only works as long as you know the relationship. It may or not have a join, or distinct.
This works regardless of what the relationship is:
p.or(p.unscope(:where).where('2 = 2'))
=> SELECT DISTINCT `posts`.* FROM `posts` WHERE ((1 = 1) OR (2 = 2))
It occurs when you try to combine two multi-active records of the same type, but one of them has a joins value or an includes value, or in your case a reference value, that the other does not.
Therefore we need to match the values between them, and I found a general way to do this without knowing the actual values in advance.
items_1 = #items.joins(:orders)
.where(orders: { user_id: current_user.id})
items_2 = #items.where(available: true)
.joins(items_1.joins_values)
.includes(items_1.includes_values)
.references(items_1.references_values)
#items = items_1.or(items_2)
just solve it!
def exec_or_statement(q1, q2)
klass = q1.klass
key = klass.primary_key
query_wrapper_1 = {}
query_wrapper_1[key] = q1
query_wrapper_2 = {}
query_wrapper_2[key] = q2
klass.where(query_wrapper_1).or(klass.where(query_wrapper_2))
end
query_1 = #items.where(available: true)
query_2 =
#items
.joins(:orders)
.where(orders: { user_id: current_user.id})
exec_or_statement(query_1, query_2)

Create hash from variables in loop

I want to end up with an array of hashes.
I am starting with an array of codes:
#codes = ['123', '456', '789']
I take each of those codes and hit an API with them and it returns values that I parse into variables in a loop, like so:
#codes.each do |x|
#y = x.get_some_data
#brand = #y[brand]
#size = #y[size]
end
Then I want to put this data into an array of hashes
merged_array = []
final_hash = #codes.map{|code| {:code => code, :brand=> #brand, :size=> #size}
merged_array << final_hash
And in a perfect world, end up with hashes that look like this in the merged_array:
{:code => '123', :brand=> 'nike', :size=> 8 }
{:code => '456', :brand=> 'adidas', :size=> 4 }
{:code => '789', :brand=> 'converse', :size=> 10 }
But when I run my script it maps the codes right, but overwrites the #brand, #size variables and just returns the values of the last loop.
Not sure how to get all my variables into the hashes?
In your code example, the variables are all declared as being instance variables, because they're prefixed with #.
However, the variables within the loop are simply working/temporary variables, not instance variables. Additionally, x.get_some_data is probably not working, since x is just the loop variable and contains 456, abc, etc., and not an object with the desired method. Thus, the following code should produce your desired result:
def get_data(codes)
result = []
codes.each do |code|
y = get_some_data(code)
result << {
code: code,
brand: y['brand'],
size: y['size']
}
end
result
end
This is a very verbose example; you can put the whole logic in map, if the return value of get_some_data permits it.
A more elegant version would utilize Enumerable#each_with_object (the Array class includes Enumereable):
def get_data(codes)
codes.each_with_object([]) do |code, result|
y = get_some_data(code)
result << {
code: code,
brand: y['brand'],
size: y['size']
}
end
end
Thanks, Cary Swoveland, for pointing this out!
This should do the work
#codes.map{|code| {:code => code, :brand => code.get_some_data['brand'], code.get_some_data['size']}}
But, I'm really not sure what String.get_some_data will give you. Eg:- '123'.get_some_data['brand']

Sort Array By Date Attribute, Ordered By Nearest to Current Time

I have an Array of Contact objects...
[#<Contact id: 371, full_name: "Don Hofton", birthday: "2013-11-07">,...]
And I need to order them by birthdays nearest to the current time AND remove objects from the array that have birthdays greater than 4 months away. Here is what I've got so far, but it's not working....
#contacts_with_birthday_data = Contact.where(:user_id => current_user.id).where("birthday IS NOT NULL")
#current_time = Time.now
#contacts_with_birthday_data.each do |c|
c.birthday = c.birthday[0..4]
c.birthday = Date.parse(c.birthday)
end
#contacts_with_birthday_data = #contacts_with_birthday_data.sort! { |a,b| b[:birthday] <=> a[:birthday] }
#contacts_with_birthday_data = #contacts_with_birthday_data.sort! { |a| a.birthday < DateTime.now }
I think you can do this all with one query:
Contact \
.where(:user_id => current_user.id)
.where("birthday > ?", 4.months.ago)
.order("birthday desc")
If 4.months.ago is used in a scope, make sure to wrap it in a lambda or Proc, or it will be calculated when the class is loaded and not on subsequent calls. This has bit me more than once!
Alternatively, in a non-Rails world, you could use the reject and sort_by methods on Enumerable:
contacts = [#your array of contacts]
contacts.reject { |c| c.birthday < 4.months.ago }.sort_by(&:birthday).reverse
If you haven't seen the syntax used in sort_by, that's actually equivalent to sort_by { |c| c.birthday }. That syntax tells Ruby to convert the birthday method to a Proc object, then call the Proc against each instance in your array.

Resources