Rails conditions, how to work around nils being dropped from arrays? - ruby-on-rails

For both basic conditions (like find statements) and the more general where clause, users have the ability to look for any of a group of conditions. For example:
User.find_by_name(["One", "Two", "Three"])
User.find_by_name_and_age(["One", "Two"],[1,2,3])
or
User.where(:bonus_id => [1,2,3])
Their is a slight inconsistance here, though. If you use
User.find_by_bonus_id([nil,1,2])
This will return uses with a bonus id of "nil", as could be expected! However, both the "find_by_...and..." format and "where" format do NOT work this way.
User.where(:bonus_id => [nil,1])
Will return ONLY those Users with a bonus_id of 1.
User.where(:bonus_id => [nil]
will return nothing all!
User.where(:bonus_id => nil)
however, works fine.
As near as I can tell, the where clause (and find_by_and method) collapse their arrays, removing any values that are non-truthy. This is a pretty significant problem for me.
Does anyone know a way to include nils in a where clause array (or a workaround), or am I going to have to end up joining multiple queries together in order to obtain the right behaviour?
Additional Notes:
- Version 3.0.11
- Nils are not actually being dropped, but rather under certain circumstances it is comparing IN (NULL) instead of IS NULL in the SQL. I'm leaving the title as is, since even though it is inaccurate, it's how the problem appears at first when encountered.

I'm not able to reproduce your problem on Rails 3.1.0. What version are you using?
Try this:
User.where(["bonus_id IN (?) OR bonus_id IS NULL", [1,2]])
Will return users where the bonus_id is either 1, 2, or null

Related

Rails - Understanding the Querying

I am new to Rails, and I am given a code, but I do not understand what it means (I am actually just trying to understand the Rails code because I am tasked to use the same logic inside another program)
here is the code
ids = [1, 2, 3]
users = User.where(account_id: ids)
output = Worksheet.where(created_by: users).as_json(only: [:created_at, :id]).group_by_week(week_start: :monday)
{|w| w["created_at"]}
i am not sure if I am following along, but from what i understand, it seems like i am querying the users with id 1, 2, 3, and finding the worksheets that are created by said users, and grouping them by week. However, I do not really understand what the 'only: [:created_at, :id]' does, but I checked through the columns, and there were columns 'created_at' and 'id' inside the worksheet table. Also, I am totally lost about what the below code is about
{|w| w["created_at"]}
and finally, is it possible to let me know what the output of the program would be like? thanks all!
The as_json(only: [:created_at, :id]) part says "convert this result to json but I only want those two columns." Documentation.
The group_by_week(week_start: :monday) takes a block, which is what the { |w| w["created_at"] } part is. It will go through each result from all the previous operations, assign each in turn to w, and then use w["created_at"] for the group_by_week function (for comparison purposes, most likely).

Why does splitting a string return a (bad) value across multiple lines in my Rails API application, but not the console?

I have a GrapeSwaggerRails API application that takes a two dates and a comma-delimited string of category IDs. It should query the database for Records with a created_at within the two given dates and a category_id that matches one of the IDs passed to it. I'm not having any trouble with the dates, so I'll skip that for now. But let's say I want Records with categories matching 8, 2, or 1. In the code, it looks like "8,2,1". In the URL, it gets appended as &categories=%228%2C2%2C1%22.
Anyway, I figured one decent way of getting this to do what I want would be to convert that string into an array of integers like this: categories = params[:categories].split(',').map(&:to_i)
But given "8,2,1", the output is this (ignore the comment):
0 # <-- ?????
2
1
Very strange. In the definition of the API, params[:categories] looks like this: "8,2,1". But params[:categories].split(',') becomes the following:
"8
2
1"
That's a bit odd, isn't it? Running the map method on that turns it into that nonsense higher up, converting the "8 to a 0 for reasons I'm hoping to find out here. I know I could probably come at this problem from a different angle and sidestep the issue, but I'd rather try to get to the root of what's going wrong, so I can learn something from it. For reference, here's what the Rails console does when I put (as far as I can tell) the same data into it:
>> "8,2,1".split(',')
#=> ["8", "2", "1"]
map then works as expected.
>> "8,2,1".split(',').map(&:to_i)
#=> [8, 2, 1]
So my question is twofold. What's going wrong with this split function? Why does it behave differently in the console?
Because params[:categories] is actually
'"8,2,1"' # <- the outer ''s are just for illustration of a string.
If you pass &categories=8%2C2%2C1 it should work as expected.

How to use START with Cypher / Neo4j 2.0

I am trying example provided in Graph Databases book (PDF page 51-52)with Neo4j 2.0.1 (latest). It appears that I cannot just copy paste the code sample from the book (I guess the syntax is no longer valid).
START bob=node:user(username='Bob'),
charlie=node:user(username='Charlie')
MATCH (bob)-[e:EMAILED]->(charlie)
RETURN e
Got #=> Index `user` does not exist.
So, I tried without 'user'
START bob=node(username='Bob'),
charlie=node(username='Charlie')
MATCH (bob)-[e:EMAILED]->(charlie)
RETURN e
Got #=> Invalid input 'u': expected whitespace, an unsigned integer, a parameter or '*'
Tried this but didn't work
START bob=node({username:'Bob'}),
(charlie=node({username:'Charlie'})
MATCH (bob)-[e:EMAILED]->(charlie)
RETURN e
Got #=> Invalid input ':': expected an identifier character, whitespace or '}'
I want to use START then MATCH to achieve this. Would appreciate little bit of direction to get started.
From version 2.0 syntax has changed.
http://docs.neo4j.org/chunked/stable/query-match.html
Your first query should look like this.
MATCH (bob {username:'Bob'})-[e:EMAILED]->(charlie {username:'Charlie'})
RETURN e
The query does not work out of the box because you'll need to create the user index first. This can't be done with Cypher though, see the documentation for more info. Your syntax is still valid, but Lucene indexes are considered legacy. Schema indexes replace them, but they are not fully mature yet (e.g. no wildcard searches, IN support, ...).
You'll want to use labels as well, in your case a User label. The query can be refactored to:
MATCH (b:User { username:'Bob' })-[e:EMAILED]->(c:User { username:'Charlie' })
RETURN e
For good performance, add a schema index on the username property as well:
CREATE INDEX ON :User(username)
Start is optional, as noted above. Given that it's listed under the "deprecated" section in the Cypher 2.0 refcard, I would try to avoid using it going forward just for safety purposes.
However, the refcard does state that you can prepend your Cypher query with "CYPHER 1.9" (without the quotes) in order to make explicit use of the older syntax.

Does ActiveRecord find_all_by_X preserve order? If not, what should be used instead?

Suppose I have a non-empty array ids of Thing object ids and I want to find the corresponding objects using things = Thing.find_all_by_id(ids). My impression is that things will not necessarily have an ordering analogous to that of ids.
Is my impression correct?
If so, what can I used instead of find_all_by_id that preserves order and doesn't hit the database unnecessarily many times?
Yes
Use Array#sort
Check it out:
Thing.where(:id => ids).sort! {|a, b| ids.index(a.id) <=> ids.index(b.id)}
where(:id => ids) will generate a query using an IN(). Then the sort! method will iterate through the query results and compare the positions of the id's in the ids array.
#tybro0103's answer will work, but gets inefficient for a large N of ids. In particular, Array#index is linear in N. Hashing works better for large N, as in
by_id = Hash[Thing.where(:id => ids).map{|thing| [thing.id, thing]}]
ids.map{|i| by_id[i]}
You can even use this technique to arbitrarily sort by any not-necessarily unique attribute, as in
by_att = Thing.where(:att => atts).group_by(&:att)
atts.flat_map{|a| by_att[a]}
find_all_by_id is deprecated in rails 4, which is why I use where here, but the behavior is the same.

Thinking Sphinx can't sort when using delta indexing

I'm not sure who is at fault here, but we have a column in our users table called last_logged_in_at that we use for sorting. This is in a Rails 2.3 project using Thinking Sphinx with delta indexes enabled.
When a record has delta set to true, it is push to the bottom even if the sorting by last_logged_in_at should put it at the top.
I tried with last_logged_in_at being a datetime, a timestamp and even an integer and the behavior is always the same.
Any ideas why?
The query looks something like:
{:populate=>true,
:match_mode=>:boolean,
:order=>"last_logged_in_at DESC, updated_at DESC",
:per_page=>20,
:with_all=>{:role_id=>17,
:state=>"activated",
:mandator_id=>9,
:profile_active=>true},
:page=>nil}
Sorry, life's crazy busy, hence slow reply.
You're filtering on a string - which Sphinx doesn't currently allow. There are ways around this, though.
Also: You're using :with_all, but :with behaves in exactly the same way in your situation. :with_all is useful when you want to match multiple values on a single attribute. For example, this query will match results where articles have any of the given tag ids:
Article.search :with => {:tag_ids => [1, 2, 3]}
But this next query matches articles with all of the given tag ids:
Article.search :with_all => {:tag_ids => [1, 2, 3]}
I realise neither of these points are directly related to your issue - however, it's best to get the query valid first, and then double-check whether the behaviour is correct or not.

Resources