I am working with some complex queries using the dynamic find_all method and reached to a point where sending a block to that find_all method would really simplify my code.
Is there any plugin or work in-progress dealing with this?
In simple terms, I'd like to do something like:
#products = Product.find_all_by_ids(ids, .....) do |p|
# do something to each product like
p.stock += 10
end
Any other guide or better way of doing this would be greatly appreciated.
Rails 2.3 introduced the find_in_batches and find_each methods (see here) for batch processing of many records.
You can thus do stuff like:
Person.find_each(:conditions => "age > 21") do |person|
person.party_all_night!
end
I use the .each method which Enumerable provides like
#products = Product.find_all_by_ids(ids, .....)
#products.each { |p| p.stock += 10 }
There are even some extensions to Enumerable that Rails provides that might help you a bit if you're doing some common stuff.
Also, don't forget to save your objects with something like p.save if you want the changes to actually persist.
What's wrong with this:
#products = Product.find_all_by_ids(ids).each do |p|
p.stock+=10
end
In case you didn't know, each returns the array passed to it.
Related
if array1.present?
method_call(array1[:ids])
end
if array2.present?
method_call(array2[:ids])
end
How to make the above code much more simple or in one line?
[array1, array].select(&:present?)
.map { |a| a[:ids] }
.each(&method(:method_call))
There are many ways to do this sort of thing in ruby. Here's one:
[array1, array2].compact.each do |array|
method_call(array[:ids])
end
compact will take the nils out of the array.
Alternatively...
[array1, array2].each do |array|
method_call(array[:ids]) if array
end
Edit: one-liner?
[array1, array2].compact.each {|a| method_call(a[:ids]) }
Though I should point out that if those are actually arrays, then :id is not a valid index. If they are hashes, then this example makes more sense.
This could be the same, but bit simpler,
[array1, array2].each do |a|
method_call(a[ids]) if a.present?
end
I think your code is perfectly fine because it is easy to read and to understand. And therefore I am not sure if it would improve by making it any shorter.
If I had to change the code (perhaps our example is just part of a long list of method calls and conditions) I would think about:
Reordering method calls and conditions
method_call(array1[:ids]) if array1.present?
method_call(array2[:ids]) if array2.present?
or introducing a helper method:
def call_method(array)
method_call(array[:ids]) if array.present?
end
call_method(array1)
call_method(array2)
Before I except these two methods I wanted to see if anyone in the community had a better idea to structure these and make the cops pass. The first one with to_s seems a bit crazy too. I was thinking of refactoring the other method but that would be a single line or two.
Thoughts?
Code Examples One:
def destroy(resource_name, id)
delete "#{resource_name.to_s.pluralize}/#{id}"
end
Code Examples Two:
def all_products
products_map = fetch(:products).map { |x| [x['id'], x] }.to_h
variants = fetch :variants
variants.group_by { |x| x['product']['resource']['id'] }.to_a.map do |product_id, product_variants|
product.merge 'variants' => product_variants if product == products_map[product_id]
end.compact
end
For Code example One, maybe this can be used:
delete [resource_name.to_s.pluralize, id].join('/')
For Code example Two, yes you definitely need to refactor it.
Maybe you need to create a separate method that does all the grouping and merging, etc. for the variants part.
I am not sure if this is a good practice, but you can create a private method for it.
Is there a more succinct way of expressing the following:
if Model.all
array = Model.all
array.each do |a|
a.info
end
end
In my case, Model.all is a helper method (get_all_of_those()).
In the view, I am displaying data in tables based on the results. a.info might be
"<div class='row'>#{a.name}</div>"
Model.all is always truthy and is always an array-like object (Strictly speaking it's ActiveRecord::Relation object in rails 4; an Array in rails 3). You can just do:
Model.all.each do |a|
a.info
end
If there are no models, the loop will not be executed even once.
(Note however, that this code doesn't do anything interesting with models, so you need to update your question with: What do you want the final result to be? There is a chance that you are looking for Model.pluck(:info))
If info is a field in the database, you could do this more efficiently with
array = Model.pluck(:info)
Try this out:
Model.all.find_each do |a|
a.info
end
Read more about find_each in the documentation.
Is there a way to make this situation more compact in rails views?
Eg I have haml
= object.count unless object.count ==0
I sort of don't like that has I'm repeating the function there, I would much rather have something like
= object.count unless ==0
Eg if I had more complex statements
= object.relations.where(attribute: "something").count unless zero?
I could split that into two lines say
- cnt = object.relations.where(attribute: "something").count
= cnt unless cnt==0
But for each situation I would have multiple lines, and storing a variable to use once sucks.
EDIT: just to elaborate I want to check if the number is 0, and if so not display anything. It looks nicer in the view that way.
UPDATE:
One of the answers made come up with a solution along these lines
class Object
def unless
self unless yield(self)
end
end
So I can call whatever object I have with a block eg. .unless{|c| c<1}
This lets me tack the conditionals on, and keeps it pretty clear what is going on :), bonus is as it's block driven I can use this on any object :P.
Thanks everyone :)
UPDATE EVEN MORE
Having |c| in the block sucked. So I looked up the api and changed it too
class Object
def unless(&block)
self unless instance_eval(&block)
end
end
So now I can use .count.unless{zero?} to accomplish this :P. Or if I have a complicated condition I can add that in with |c| etc.
If object is an array you can use object.empty? (or object.any? for the reverse case)
Just create a view helper:
def display_count_or_nothing(array)
array.count unless array.count == 0
end
In the view you can use it like this:
<%= display_count_or_nothing(array) %>
i think the following is nice and clear, although i hate the variable "object",
it would be much nicer if the name of the variable described the contents of the array (as plural)
= object.count unless object.empty?
If this is only about count, you can monkey patch Enumerable:
module Enumerable
def count_or_empty_string
self.any? ? self.count : ''
end
end
If object is an enumerable, you can do this:
= object.count_or_empty_string
This will return an "" if object.count == 0 else it will return an integer. So there is no need for unless or if in your HAML anymore.
I'm on Rails 3,and I have a SQL query composed of a few joins that I've built up in Arel. I want to run this query from a method in one of my models, but I'm not sure of how to do this. The arel object turns out to be of type Arel::InnerJoin, and I want to retrieve an array of all objects returned from that query. Do I run ModelName.find_by_sql(my_arel_query_object) in order to do that? Or do I run my_arel_query_object.each {...} and iterate over each tuple in order to pop them into an array manually?
I hope I'm making myself clear. Any insight would be greatly appreciated. Thanks.
Updated: Here is the code I use within my user model:
def get_all_ingredients
restaurants = Table(:restaurants)
meals = Table(:meals)
ingredients = Table(:ingredients)
restaurants_for_user = restaurants.where(restaurants[:user_id].eq(self.id))
meals_for_user = restaurants_for_user.join(meals).on(restaurants[:id].eq(meals[:restaurant_id]))
ingredients_for_user = meals_for_user.join(ingredients).on(meals[:id].eq(ingredients[:meal_id]))
return Ingredient.find_by_sql(ingredients_for_user.to_sql)
end
What I'm trying to do here is get all ingredients used in all the meals offered for each restaurant the user owns. The ingredients_for_user variable represents the Arel query that I wish to run. I'm just not sure how to run & return all the ingredients, and the Ingredient.find_by_sql... just doesn't seem right.
end
I don't understand why you wouldn't just do the following . . .
Ingredient.joins({:meals=>:restaurants}).
where(["restaurants.user_id = ?",self.id])
Arel objects (or, more specifically, Arel::Relation objects) represent a query in relational algebra. The query is executed the first time you try to access its elements, and it acts as a result set as before.
For example, if you have a query like
users.join(:photos).on(users[:id].eq(photos[:user_id]))
you can, iterate over the photos in a view:
<% users.each do |user| %>
<% users.photos.each do |photo| %>
<%= photo.name %>
<% end %>
<% end %>
The example was taken from the Arel's README, which may help you understand better.
Methods that return objects (like your get_all_ingredients method) should be class methods. It's not 100% obvious how to do this in Ruby, but one way is to use the self. prefix when declaring your method. (This self. makes sense eventually when you dive into what Rubyists often call the eigenclass, but for now we can just know that that's how it's done).
Here's what your method should look like in your Ingredients model:
def self.get_all_ingredients
# insert your code from your question here
end
Then call your method in your view like:
<%- Ingredients.get_all_ingredients.each do |current_ingredient| %>
# do your things here
<% end %>