How to execute complex Arel query in Rails 3 from Model - ruby-on-rails

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 %>

Related

Neo4j gem - Plucking multiple nodes/relationships

This may not be possible as it is right now but I have a query like so
events = Event.as(:e).where("(( e.date_start - {current_time} )/{one_day_p}) < 1 ").users(:u, :rel).where("rel.reminded = {reminded_p}" ).params(reminded_p: false, one_day_p: one_day, current_time: time).pluck(:e,:u, :rel)
The goal is to obtain the events where the start_date is less than a day away. Hypothetically I've tried to pluck the event, the user and the relationship. I need to do a different action with each.
I need to get all the users for each event and perform an email action for each user. In that action, the email needs to have access to that event.
Next, I need to update the rel.reminded property to true.
Is there a way to pluck all these simultaneously to be able to organize and perform these tasks? I started doing this individually but I have to make extra queries to make it happen. e.g.
events = Event.as(:e).where("(( e.date_start - {current_time} )/{one_day_p}) < 1 ").users(:u, :rel).where("rel.reminded = {reminded_p}" ).params(reminded_p: false, one_day_p: one_day, current_time: time).pluck(:e)
then I have to
events.each do |event|
# do stuff
end
# do another query that is associated and do more stuff
Update:
Incorporating the answers, I have something like this without the cleaned up time method yet. I added in a where search for user as I will need to factor that in later in the email function. So I am not sure if it's more efficient to include it in the query vs doing it outside per user.
#collection = Event.as(:e).where("(( e.date_start - {current_time} )/{one_day_p}) < 1 ").users(:u).where(setting_reminder: true).rel_where(reminded: false ).params(one_day_p: one_day, current_time: time).pluck(:e, 'COLLECT(u)', 'COLLECT(rel)')
but rel is not defined with this query.
I also wanted to chime in on a couple of points. You should be able to use ActiveSupport to make part of the query easier like this:
Event.as(:e).where("date_start < {date_threshold}").params(1.day.from_now)
Also, to make it less cluttered I don't see any problem in putting .where("rel.reminded = false") (or using rel_where(reminded: false) as Chris suggested). This isn't data passed in from the user and it doesn't change at different times when you call the query, so it should be just as efficient.
You also may want to use the new query chaining in the v4 of the gem to define a unreminded_users method like this:
class Event
def self.unreminded_users
all.rel_where(reminded: false)
end
end
I've actually not tried doing a rel_where like that in a query chain, but I suspect it will work. Then you'll just have this:
Event.as(:e).where("e.start_date < {date_threshold}").params(date_threshold: 1.day.from_now)
.users(:u, :rel).rel_where(reminded: false)
.pluck(:e,:u, :rel)
First off, if you're using v4 of the gem, use rel_where instead of where with a string for your relationship!
You should be able to change your pluck to pluck(:e, 'COLLECT(u)', 'COLLECT(rel)') and it'll give you a big enumerable of events, users, and rels, all grouped in parent arrays based on event. It'd be organized like this:
[
[event1, [user1a, user1b, user1c], [rel1a, rel1b, rel1c]],
[event2, [user2a, user2b, user2c], [rel2a, rel2b, rel2c]]
]
The position of each rel matches its user, so rel1a is the relationship between the event and user1a.
You can set that to #collection = my_big_query and then do #collection.each do |event, users, rels| in your view to loop through. You'd do each_with_index on users and the index of the user would correspond to the position within rels. It'd look something like:
<%= #collection.each do |event, users, rels| %>
<%= event.name %>
<% users.each_with_index do |user, i| %>
<%= user.name %> said they were attending at <%= rels[i].confirmed_at %>.<br />
<% end %>
<% end %>

Rails db query to find and sort posts

My app has designs that users can like (vote, using acts_as_voteable). To find a design's like count in the view, you use
#design.votes.count
I'm making a popular page to showcase the most popular designs based on the number of votes they have. I only want designs that has at least 5 votes to them. Right now, I had that in the view but I want to push that into the controller. My controller, thus far, looks like this which shows all the designs and sorts them in order of most votes.
def popular
#designs = Design.all
#designs.sort! {|t1, t2| t2.votes.count <=> t1.votes.count}
end
Now i just want to make sure the designs have a minimum vote count of 5.
Previously, I was doing this the wrong way and putting it in my view by putting this inside my Design loop
<% if design.vote.count > 5 %>
...
<% end %>
Thanks!
First of all, the behavior you want is better defined in the Design model and not in the controller since it deals with data. So in your Design model, add the following code:
scope :most_popular, -> do
results = select {|design| design.votes.count > 4 }
results.sort! {|t1, t2| t2.votes.count <=> t1.votes.count}
end
Adding the two scope methods above in your Design model, you could do this in your controller code:
def popular
#designs = Design.most_popular
end
Your controller code ends up being a lot cleaner and you have scope methods that you can reuse anywhere else you need them. :)
Hope that helps!
You can use a having() clause. See: http://guides.rubyonrails.org/active_record_querying.html#having
For example: Design.joins(:votes).group('votes.design_id').having('votes.count > 5').order('votes.count')
Edit
You can also just use a where clause. For example, for the first design:
Design.first.votes.where('count > 5')
Example for multiple designs:
Design.all.map{ |a| a.votes.where('count > 5').count }.sort! # returns a sorted array with all vote counts

Fill array with nils for when nothing returned from query in ruby (RoR)

I have a model called foo with a date field.
On my index view, I am showing a typical "weekly view" for a specified week. To put the data in my view, I loop through each day of the specified week and query the data one day at time. I do this so that I can make sure to put a NIL on the correct day.
foos_controller.rb
for day in 0..6
foo = Foo.this_date(#date+day.days).first
#foos[day] = foo
end
index.html.haml
- for day in 0..6
%li
- if #foos[day].nil?
Create a new foo?
- else
Display a foo information here
Obviously, there's a lot of things wrong here.
I should find someone smart member to tell me how to write a good query so that I only have to do it once.
I should not have any if/else in my view
My goal here is to either show the content if the it is there for a particular day or show a "create new" link if not.
thanks for the help in advance!!
First, I have no idea what this_date actually does, but I'll assume it's retrieving a record with a specific date from your datastore. Instead of doing 7 queries, you can condense this into one using a date range:
Foo.where(date: (#date..(#date + 6.days)))
You can tack on a .group_by(&:date) to return something similar to the hash you are manually constructing, but using the actual dates as keys instead of the date offset.
To iterate over the dates in the view, I would recommend using Hash#fetch, which allows you to define a default return when a key is not present, e.g:
hash = { :a => 1, :b => 2 }
hash.fetch(:a){ Object.new } #=> 1
hash.fetch(:c){ Object.new } # #<Object:...>
The question now is what object to substitute for nil. If you want to avoid using conditionals here, I'd recommend going with the NullObject pattern (you could involve presenters as well but that might be a bit overkill for your situation). The idea here is that you would create a new class to substitute for a missing foo, and then simply define a method called to_partial_path on it that will tell Rails how to render it:
class NullFoo
def to_partial_path
"null_foos/null_foo"
end
end
You'll need to create partials at both app/views/foos/_foo.html.erb and app/views/null_foos/_null_foo.html.erb that define what to render in each case. Then, in your view, you can simply iterate thusly:
<% (#date..(#date + 6.days)).each do |date| %>
<%= render #foos.fetch(date){ NullDate.new } %>
<% end %>
Is this appropriate for your situation? Maybe it's also a bit overkill, but in general, I think it's a good idea to get in the habit of avoid nil checks whenever possible. Another benefit of the NullObject is that you can hang all sorts of behavior on it that handle these situations all throughout your app.

Should I symbolize keys?

1) I am grabbing some records for the DB in HAML to display, and the attributes method on each row returns a hash. The hash's keys are strings. Should I turn those keys into symbols? I am not sure the call to symbolize_keys is worth it. I.e.,
%td #{app['comment']}
or
%td #{app[:comment]
2) I am trying to symbolize the array of hashes I return, but it is not working:
rows = Comment.all(:order => 'created DESC')
result = rows.each_with_object([]) do |row, comments|
comments << row.attributes.symbolize_keys
end
Is it not actually pushing the symbolized hash into the comments array? I also tried symbolize_keys!, and that did not help. What am I doing wrong?
Since you're using Rails, you have access to HashWithIndifferentAccess so you can bypass your "strings or symbols" issue quite easily by allow both:
h = HashWithIndifferentAccess.new(some_model.attributes)
puts h['id'] # Gives you some_model.id
puts h[:id] # Also gives you some_model.id
Your each_with_object approach:
result = rows.each_with_object([]) do |row, comments|
comments << row.attributes.symbolize_keys
end
should work fine so I think your problem with that lies elsewhere.
Do you have a reason for using ActiveRecord::Base#attributes[your_attribute] instead of ActiveRecord::Base#your_attribute directly? You didn't mention a reason.
ActiveRecord::Base automatically sets up accessors for your database fields:
object = Model.new
object.your_column = "foo" # Writer
object.your_column # Reader
You should be able to use the reader in your views instead of accessing the value through ActiveRecord::Base#attributes.
Update:
I'm not sure if this is what confuses you.
Comment.find(:all) already retrieves all columns values for those rows in your database and stores them in your Comment objects (which we assign to #comments below). The values are already stored in your Comment objects, so you may already use them in your views as you please.
In your controller, if you have:
def index
#comments = Commend.find(:all) # Fetch columns and rows.
end
you can do this in your HAML view:
- #comments.each do |comment| # Iterate through array of Comment objects
%tr
%td= comment.comment # Use value for "comment" column.
you can add hook, which symbolizes keys after model load:
class YourModel < ApplicationRecord
after_initialize do |rec|
attributes["some_json_field"].symbolize_keys! if attributes.key? "some_json_field"
end
end

Sending a code block to a find_all dynamic method

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.

Resources