Neo4j gem - Plucking multiple nodes/relationships - ruby-on-rails

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

Related

How to pass default filter into Filterrific get

I finally got my filterrific get working and its a great gem, if not a little complex for a noob like me.
My original index page was filtering the active records based on those nearby to the user like this:
def index
location_ids = Location.near([session[:latitude], session[:longitude]], 50, order: '').pluck(:id)
#vendor_locations = VendorLocation.includes(:location).where(location_id: location_ids)
#appointments = Appointment.includes(:vendor).
where(vendor_id: #vendor_locations.select(:vendor_id))
end
So this pulls in all of the Appointments with Vendors in the area, but how do I pass this over to the Filterrific search:
#filterrific = initialize_filterrific(
params[:filterrific],
select_options:{ sorted_by: Appointment.options_for_sorted_by, with_service_id: Service.options_for_select },
) or return
#appointments = #filterrific.find.page(params[:page])
respond_to do |format|
format.html
format.js
end
It seems like the Filterrerrific is loading ALL of the appointments by default, but I want to limit to the ones nearby. What am I missing?
What you appear to be missing is a param default_filter_params to filterrific macro in the model. (Your question didn't mention that you made any adjustments to the VendorLocation model, since that is the object that you want to filter, that's where the macro should be called. Maybe you just omitted it from your question...)
From the model docs:
filterrific(
default_filter_params: { sorted_by: 'created_at_desc' },
available_filters: [
:sorted_by,
:search_query,
:with_country_id,
:with_created_at_gte
]
)
You probably found this already, it was on the first page of the documentation, but there's more important stuff in the example application that you need (I ran into this too, when I was just recently using Filterrific for the first time.)
The information on the start page is not enough to really get you started at all.
You have to read a bit further to see the other ways you may need to change your models, model accesses, and views in order to support Filterrific.
The part that makes the default filter setting effective is this default_filter_params hash (NOT select_options, which provides the options for "select" aka dropdown boxes. That's not what you want at all, unless you're doing a dropdown filter.) This hash holds a list of the scopes that need to be applied by default (the hash keys) and the scope parameter is used as the hash value.
That default_filter_params hash may not be the only thing you are missing... You also must define those ActiveRecord scopes for each filter that you want to use in the model, and name these in available_filters as above to make them available to filterrific:
scope :with_created_at_gte, lambda { |ref_date|
where('created_at >= ?', ref_date)
end
It's important that these scopes all take an argument (the value comes from the value of the filter field on the view page, you must add these to your view even if you want to keep them hidden from the user). It's also important that they always return ActiveRecord associations.
This is more like what you want:
scope :location_near, lambda { |location_string|
l = Location.near(location_string).pluck(:id)
where(location_id: l)
end
The problem with this approach is that in your case, there is no location_string or any single location variable, you have multiple coordinates for your location parameters. But you are not the first person to have this problem at all!
This issue describes almost exactly the problem you set out to solve. The author of Filterrific recommended embedding the location fields into hidden form fields in a nested fields_for, so that the form can still pass a single argument into the scope (as in with_distance_fields):
<%= f.fields_for :with_distance do |with_distance_fields| %>
<%= with_distance_fields.hidden_field :lat, value: current_user.lat %>
<%= with_distance_fields.hidden_field :lng, value: current_user.lng %>
<%= with_distance_fields.select :distance_in_meters,
#filterrific.select_options[:with_distance] %>
<% end %>
... make that change in your view, and add a matching scope that looks something like (copied from the linked GitHub issue):
scope :with_distance, -> (with_distance_attrs) {
['lng' => '-123', 'lat' => '49', 'distance_in_meters' => '2000']
where(%{
ST_DWithin(
ST_GeographyFromText(
'SRID=4326;POINT(' || courses.lng || ' ' || courses.lat || ')'
),
ST_GeographyFromText('SRID=4326;POINT(%f %f)'),
%d
)
} % [with_distance_attrs['lng'], with_distance_attrs['lat'], with_distance_attrs['distance_in_meters']])
}
So, your :with_distance scope should go onto the VendorLocation model and it should probably look like this:
scope :with_distance, -> (with_distance_attrs) {
lat = with_distance_attrs['lat']
lng = with_distance_attrs['lng']
dist = with_distance_attrs['distance']
location_ids = Location.near([lat, lng], dist, order: '').pluck(:id)
where(location_id: location_ids)
end
Last but not least, you probably noticed that I removed your call to includes(:location) — I know you put it there on purpose, and I didn't find it very clear in the documentation, but you can still get eager loading and have ActiveRecord optimize into a single query before passing off the filter work to Filterrific by defining your controller's index method in this way:
def index
#appointments = Appointment.includes(:vendor).
filterrific_find(#filterrific).page(params[:page])
end
Hope this helps!

How does one get the "next" record from database sorted by a specific attribute without loading all the records?

Here's the situation:
I have an Event model and I want to add prev / next buttons to a view to get the next event, but sorted by the event start datetime, not the ID/created_at.
So the events are created in the order that start, so I can compare IDs or get the next highest ID or anything like that. E.g. Event ID 2 starts before Event ID 3. So Event.next(3) should return Event ID 2.
At first I was passing the start datetime as a param and getting the next one, but this failed when there were 2 events with the same start. The param start datetime doesn't include microseconds, so what would happen is something like this:
order("start > ?",current_start).first
would keep returning the same event over and over because current_start wouldn't include microseconds, so the current event would technically be > than current_start by 0.000000124 seconds or something like that.
The way I got to work for everything was with a concern like this:
module PrevNext
extend ActiveSupport::Concern
module ClassMethods
def next(id)
find_by(id: chron_ids[current_index(id)+1])
end
def prev(id)
find_by(id: chron_ids[current_index(id)-1])
end
def chron_ids
#chron_ids ||= order("#{order_by_attr} ASC").ids
end
def current_index(id)
chron_ids.find_index(id)
end
def order_by_attr
#order_by_attr ||= 'created_at'
end
end
end
Model:
class Event < ActiveRecord::Base
...
include PrevNext
def self.order_by_attr
#order_by_attr ||= "start_datetime"
end
...
end
I know pulling all the IDs into an array is bad and dumb* but i don't know how to
Get a list of the records in the order I want
Jump to a specific record in that list (current event)
and then get the next record
...all in one ActiveRecord query. (Using Rails 4 w/ PostgreSQL)
*This table will likely never have more than 10k records, so it's not catastrophically bad and dumb.
The best I could manage was to pull out only the IDs in order and then memoize them.
Ideally, i'd like to do this by just passing the Event ID, rather than a start date params, since it's passed via GET param, so the less URL encoding and decoding the better.
There has to be a better way to do this. I posted it on Reddit as well, but the only suggested response didn't actually work.
Reddit Link
Any help or insight is appreciated. Thanks!
You can get the next n records by using the SQL OFFSET keyword:
china = Country.order(:population).first
india = City.order(:population).offset(1).take
# SELECT * FROM countries ORDER BY population LIMIT 1 OFFSET 1
Which is how pagination for example often is done:
#countries = Country.order(:population).limit(50)
#countries = scope.offset( params[:page].to_i * 50 ) if params[:page]
Another way to do this is by using would be query cursors. However ActiveRecord does not support this and it building a generally reusable solution would be quite a task and may not be very useful in the end.

Rails3 - Multiple queries or single query in controller action

Hopefully a simple question around a rails best-practice.
Let's keep this super simple; say I have a task model that has an ID, description and status.
In my controller I have an index action to return all tasks
def index
#tasks = Task.all
end
My question is, in my view, suppose I want to display the tasks in separate HTML tables according to their status.
What is the best practice?
a) Query the database multiple times in the index action, ie
def index
#draft_tasks = Task.where(status: "Draft")
#approved_tasks = Task.where(status: "Approved")
#closed_tasks = Task.where(status: "Closed")
end
b) Query the database once, and filter in the contoller action
def index
tasks = Task.all
#draft_tasks = tasks.#somethinghere
#approved_tasks = tasks.#somethinghere
#closed_tasks = tasks.#somethinghere
end
c) Filter in the view
<% #tasks.each do |k, v| %>
<% some if statement searching for the status I want %>
# Some code to output the table
<%end%>
<%end%>
or
d) Something else?
The generally accepted best practices here are to keep controller methods thin and to keep logic out of the view. So with that in mind, one possible way to do this would be:
# model
class Task
scope :drafts, where(:status => "Draft")
scope :approved, where(:status => "Approved")
scope :closed, where(:status => "Closed")
end
# controller
def index
#draft_tasks = Task.drafts
#approved_tasks = Task.approved
#closed_tasks = Task.closed
end
This will make 3 queries to the database, which could become a performance concern down the road, but if that does happen, you can optimize it at the model level (e.g. by defining class methods drafts, approved, and closed where the first one called prefetches everything). It's less elegant though, so don't prematurely optimize.
This is a loaded question with no one best practice in my opinion. Given the case you have stated (display a table for each status) I would use the following thought process:
I would generally avoid case A when you're just dealing with one Model type. I try to limit the number of database queries when possible
Case B is what I would probably use if the view needs to display different markup depending on the status of a task.
I would usually tend towards case C if the markup is the same for each status. You can use the group_by function for this:
When the amount of information on your page starts to get larger and more complicated, you can start looking at extracting some logic out of the controller and into another object (common terms for this object would be a presenter or decorator). This can make testing some of your presentation logic easier by separating it from the controller and keeping your controllers 'thin'. But for the case you've given, I'd stick with option b or c.
In the simple case where the number of tasks is limited, I would do only a single query to retrieve them, and then separate them as follows:
tasks = Task.all
#draft_tasks = tasks.select { |x| x.status == 'Draft' }
#approved_tasks = tasks.select { |x| x.status == 'Approved' }
#closed_tasks = tasks.select { |x| x.status == 'Closed' }
Furthermore, depending on the bendability of your requirements, I would even render them in a single table with a clear visual marker what the state is (e.g. background-colour or icons). Then there would not even be a reason to separate the tasks beforehand (but I can imagine this would break your UI completely).
None of the above is valid once the number of tasks becomes larger, and you will need to apply pagination, and you need to display three different tables (one for each state).
In that case you will need to use the three separate queries as answered by #Ben.
Now UI-wise, I am not sure how you can paginate over three different sets of data at once. So instead I would use a single table showing all the states, and offer the option to filter on the status. In that case at least it is clear for the user what pagination will mean.
Just my two cents, hope this helps.
option a) seems better just because database can cache the query for you and stuff, so it should be faster.

Why does this always return true? Rails

def follows(follower, followed)
follow = Follows.where("follower = ? AND followed = ?", follower, followed)
if follow
true
else
false
end
end
Here is my view code:
<% if current_user.id == #user.id%>
<p>This is you!</p>
<% else %>
<% if follows(current_user.id, #user.id)%>
<p>You already follow <%= #user.username %>
<% else %>
<p><%= link_to "Follow!", follow_path(#user.id) %></p>
<% end %>
<% end %>
I want to check if a user follows another, so wrote this. It takes in two user ids, and queries the Database, and should return true when a match is found and false otherwise. But it always return true. Why is this?
Let's start with some style and design issues and end with the actual answer:
Models are singular by convention. Doing otherwise will only cause you more work. In this case, I would suggest Following as a suitable name, as in "a user has many followings".
Foreign keys should end with _id. Doing otherwise will only cause you more work. So follower_id and followed_id.
Methods that are intended to be used for their true/false nature ("query methods") should end with a ?, so follows? instead of follows,
Your if statement is redundant and can be safely removed once the condition does the right thing. In ruby, in the context of conditionals, we care more about whether things evaluate to true/false than whether they are literally true or false. This means that anything other than nil or false will be "truthy".
The fact that your method depends entirely on information known to User objects indicates that it would be better to hang it off of those objects, for instance current_user.follows? other_user.
You are duplicating behavior that would already be provided to you by using associations.
Finally, taking all of these things into consideration, the answer:
class User < ActiveRecord::Base
has_many :followings, :class_name => 'Following', :foreign_key => 'followed_id'
has_many :followers, :through => 'followings'
def follows?(other)
other.followed_by? self
end
def followed_by?(other)
followers.include? other
end
end
NB: The use of the followed_by? method here is a use of double dispatch that prevents the (minor) Law of Demeter violation of one user knowing directly about the state of another user's followers. Rather, the first user object asks the second user object a direct question ("Are you followed by me?") and bases the result off of the answer. (It is also likely to be a useful method in and of itself.)
The reason it always returns true is that, even when no records are found, where() is returning an empty array. An empty array is "true". In other news, the structure:
if (condition)
true
else
false
end
Can be replaced by:
condition
follow is actually an instance of ActiveRecord::Relation rather than the result set of your query. To work out find out if any rows would be returned by the query use follow.count. Eg.
if follow.count > 0
true
else
false
end
You can use present?. Your code should be
if follow.present?
true
else
false
end
#rein Heinrichs answer is superb. He gives you the best Rails way to solve it. But i would like to explain why what you wrote does not work, and how you should fix that.
Follows.where(...)
returns an array, the easy way to verify this yourself is to run that line in the rails console (type rails c in the console).
An array, even an empty one, is not nil and will always evaluate to true.
So to return a boolean depending on the fact whether or not any followers are found, just check for the amount of items inside the result of the where (use size > 0 or present?)
So your follows function could then have been rewritten as:
def follows(follower, followed)
Follows.where("follower = ? AND followed = ?", follower, followed).present?
end
and this is actually quite readable as well. Hope this helps.

How to execute complex Arel query in Rails 3 from Model

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

Resources