map(&:id) work but not pluck(:id) - sql

In part of my code I need to grab users's id but pluck doesn't grab them. Only map(&:id) can do it. I was wondering why.
I've wrote a quick and dirty block of code to find what's happen
def remove_users user_ids
p users_to_remove.class
users_to_remove = self.users.where(id: user_ids)
if users_to_remove.present?
self.users -= users_to_remove
self.save
p users_to_remove.class
p users_to_remove
Rails::logger.info "\n#{users_to_remove}\n"
users_to_remove_map = users_to_remove.map(&:id)
p users_to_remove_map
Rails::logger.info "\nmap id: #{users_to_remove_map}\n"
users_to_remove_pluck = users_to_remove.pluck(:id)
p users_to_remove_pluck
Rails::logger.info "\npluck id: #{users_to_remove_pluck}\n"
#...
end
self.user_ids
end
Who return in my test.log
#<User::ActiveRecord_AssociationRelation:0x007fb0f9c64da8>
map id: [144004]
(0.3ms) SELECT "users"."id" FROM "users" INNER JOIN "groups_users" ON "users"."id" = "groups_users"."user_id" WHERE "groups_users"."group_id" = $1 AND "users"."id" IN (144004, 144005) [["group_id", 235819]]
pluck id: []
And in my test
User::ActiveRecord_AssociationRelation
User::ActiveRecord_AssociationRelation
#<ActiveRecord::AssociationRelation [#<User id: 144004, created_at: "2015-08-06 08:55:11", updated_at: "2015-08-06 08:55:11", email: "user_2#test.com", token: "rpf5fqf5bs1ofme6aq66fwtcz", reset_password_token: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, disabled: false, first_name: "John_2", last_name: "Rivers_4", is_owner: false, organisation_id: 235826, encrypted_password: "$2a$04$8ev1j...f6ICL.ezS....", reset_password_sent_at: nil, default_language: nil, uid: nil, api_key: "rmhh...noyn">]>
[144004]
[]
The strange thing is. I have user with id. map can get them. pluck not.
I don't understand sql log also. How can I get map id result without any select in sql log? Caching ?

pluck doesnt work on an array, it works on an ActiveRecord::Relation, it's goal is to avoid to make a full query to only get the ids.
Once you've retrieved all columns from db, you can just map what you need.
You create the array when you do self.users -= users_to_remove, or maybe even when you do .present?, since you should use .exists?

This line:
users_to_remove = self.users.where(id: user_ids)
Doesn't fire off SQL query immediately. It sends the request whenever you need some details of these users. And it caches the result in SQL cache (so when the same request goes to DB again, it intercepted by Rails and never reaches the database).
So when you call:
users_to_remove.map(&:id)
It uses that cached result. But when you use
users_to_remove.pluck(:id)
It re-fetches the result, because the actual SQL query differs. With #map it is SELECT * FROM ..., and with #pluck it's SELECT id FROM.... And when query reaches the database, IDs doesn't belong to 'self' any longer (you deleted them right before that), so they aren't returned.

Related

Laravel where, orWhereHas and whereNotIn

Hello great people of SO!
I hope you all have a good day and have a good health
Note: I'm not good at SQL
Sorry for bad english, but I will try my best to explain my issue
I'm using Laravel v8.x for my app, and after setting up model relationships, events, queues, etc, now I'm working for SQL
ATM, I have 2 Models,
User
Post
Relationships:
User hasMany Post
User belongsToMany User (Block)
User belongsToMany User (Follow)
Post belongsTo User
Database:
5 record for User
2 record for Block
3 records for Post
Table: (Using faker)
users
[
{ id: 1, name: 'Jonathan Beatrice', username: 'kiana.fay', ... },
{ id: 2, name: 'Lacey Kirlin', username: 'kenna.turner', ... },
{ id: 3, name: 'Alexander Schiller', username: 'cassandra95', ... },
{ id: 4, name: 'Daniel Wickozky', username: 'nkoepp', ... },
{ id: 5, name: 'Maymie Lehner', username: 'frami.felton', ... }
]
block
[
{ id: 1, by_id: 1, to_id: 2 }, // User #1 block user #2
{ id: 2, by_id: 4, to_id: 1 } // User #4 block user #1
]
posts
[
{ id: 1, user_id: 2, body: 'Test post', ... },
{ id: 2, user_id: 5, body: 'Lorem ipsum dolor sit amet ...', ... },
{ id: 3, user_id: 4, body: 'ABCD festival soon! ...', ... },
]
Everything works fine and smooth
Now that I want to implement search system, I have a problem, since I'm not good with SQL
Here's my code
SearchController.php
use ...;
use ...;
...
public function posts(Request $request)
{
// For testing purpose
$user = User::with(['userBlocks', 'blocksUser'])->find(1);
// Get all id of user that $user block
// return [2]
$user_blocks = $user->userBlocks->pluck('pivot')->pluck('to_id')->toArray();
// Get all id of user that block $user
// return [4]
$blocks_user = $user->blocksUser->pluck('pivot')->pluck('by_id')->toArray();
// Merge all ids above (must be unique())
// return [2, 4]
$blocks = array_merge($user_blocks, $blocks_user);
// .../search?q=xxx
$query = $request->query('q');
$sql = Post::query();
// Search for posts that has `posts`.`body` LIKE ? ($query)
$sql->where('body', 'LIKE', "%$query%");
// This is where I got confused
$sql->orWhereHas('user', function ($post_user) use ($blocks, $query) {
$post_user
->whereNotIn('id', $blocks) // Exclude posts that has user and their id not in (x, x, x, x, ... ; $block variable above)
->where('name', 'LIKE', "%$query%") // Find user that has name LIKE ? ($query)
->orWhere('username', 'LIKE', "%$query%"); // or Find user that has username LIKE ? ($query)
});
$sql->orderBy('created_at', 'DESC');
$sql->with(['user']);
$posts = $sql->simplePaginate(10, ['*'], 'p');
return $posts;
}
I run the code, .../search?q=e
Note:
All users has alphabet E in their names
And also all posts has alphabet E in their body
We (as User #1), block User #2, and User #4, block us (User #1)
Result: Controller returned all posts
This is the query when I use DB::enableQueryLog() and DB::getQueryLog()
SELECT
*
FROM
`posts`
WHERE `body` LIKE ?
AND EXISTS
(SELECT
*
FROM
`users`
WHERE `posts`.`user_id` = `users`.`id`
AND (
`id` NOT IN (?)
AND `username` LIKE ?
OR `name` LIKE ?
))
ORDER BY `created_at` ASC
LIMIT 11 OFFSET 0
Goal: Search all posts that has body LIKE ?, OR posts that has user; username LIKE ? or name LIKE ? (But also exclude the user we block and the user that block us
Thanks in advance
If there's any unclear explanation, I will edit it A.S.A.P
If I run on my recent laravel install, with my proposed change for one of your issues, version 7.19.1, I get this query:
SELECT
*
FROM
`posts`
WHERE `body` LIKE ?
OR EXISTS <- line of interest
(SELECT
*
FROM
`users`
WHERE `posts`.`user_id` = `users`.`id`
AND (
`id` NOT IN (?)
AND (`username` LIKE ?
OR `name` LIKE ?) <- extra brackets ive added
))
ORDER BY `created_at` ASC
LIMIT 11 OFFSET 0
Have a look at the line of interest, and compare it with the query your version of laravel is running. The AND EXISTS line is being incorrectly generated by laravel. OrWhereHas isnt behaving correctly in your version, I can't find the release number to see where it was fixed.
Id recommend upgrading to latest if possible, but thats not always an option. I've had a dig around, and it looks like the user in this question here encountered a similar problem:
WhereHas() / orWhereHas not constraining the query as expected
You can try moving your $sql->with(['user']); to before you OrWhereHas clause. I'm not sure if that will change it to OR, but its worth a try.
Second thing, I've added whereNested to your OR clause to ensure the precedence is correct, which adds the extra brackets in the query above, as in you dont want:
(`id` NOT IN (1, 2, 3)
AND `name` LIKE % test %)
OR `username` LIKE % test %
Since then it would include your blocked posts in the exists clause.
So final changes look like this, which I think fufills your description:
$sql->with(['user']); //deleted from original position and move here
$sql->where('body', 'LIKE', "%$query%")->whereNotIn('id', $blocks); //additional line
$sql->orWhereHas('ambience', function ($post_user) use ($blocks, $query) {
$post_user
->whereNotIn('id', $blocks);
$post_user->whereNested(function($post_user) use ($query) { //new bit
$post_user->where('name', 'LIKE', "%$query%")
->orWhere('username', 'LIKE', "%$query%");
});
});

Find object with value in its array

I'm using lokijs, which has "mongo-similar" query language.
devices.insert({key:'d1', name:'Device1', users:['u1'], status: 'OK', current_wl:'123'})
devices.insert({key:'d2', name:'Device2', users:['u1','u1'], status: 'OK', current_wl:'123'})
devices.insert({key:'d3', name:'Device3', users:['u2','u3'], status: 'OK', current_wl:'123'})
devices.insert({key:'d4', name:'Device4', users:['u1','u2','u3','u4'], status: 'OK', current_wl:'123'})
My attempt to find a device having user 'u1' in its array users returns emty list:
a= devices.find( {users:{ "$in" : ["u1"] }} )
console.log("A", a);
Is the query correct, if the problem was for mongodb?
Is there another way to do it in mongo?
Is there another way to do it in lokijs?
I'm not sure about lokijs, but that's the correct query in Mongo.
If you're only ever going to query for documents that contain a single specific item in their "users" array, a simpler query for this case in Mongo would be:
db.collection.find({ users: "u1" })
I found a solution (or a work-around) using where:
a= devices.where( function(obj){
return obj.users.indexOf('u1') > -1;
}
);

Rails 3, prepared query not working with nil value

Here is my query
query = "(description = ? AND address1 = ?) AND (city = (?) OR old_city = (?) OR cb_city = (?))"
Input data :
office_data = {"description"=>"Europe HQ", "city"=>"Dublin", "street_1"=>nil}
But When I queries as
office = company.offices.where(query, office_data["description"], office_data["street_1"], office_data["city"], office_data["city"], office_data["city"]).first
Equivalent SQL:
SELECT "offices".* FROM "offices" WHERE "offices"."company_id" = 6 AND ((description = 'Europe HQ' AND address1 = NULL) AND (city = ('Dublin') OR old_city = ('Dublin') OR cb_city = ('Dublin'))) LIMIT 1
Getting result as
nil
Though I am records in company.offices
As
[#<Office id: 393, company_id: 6, description: "Europe HQ", address1: nil, address2: nil, zip_code: nil, city: nil, state_code: nil, country_code: nil, latitude: nil, longitude: nil, created_at: "2014-08-19 15:13:58", updated_at: "2014-08-19 15:13:58", cb_city: "Dublin", cb_state_code: nil, cb_country_code: "IRL", old_city: nil, region_code: nil, is_hq: nil, cb_region: "Dublin", cb_updated: "2014-04-20 10:53:24", city_uuid: "97c70aa17568ca5375122f181f0484a7", city_path: "location/dublin/97c70aa17568ca5375122f181f0484a7">, #<Office id: 389, company_id: 6, description: "Europe HQ", address1: nil, address2: nil, zip_code: nil, city: nil, state_code: nil, country_code: nil, latitude: nil, longitude: nil, created_at: "2014-08-19 15:02:44", updated_at: "2014-08-19 15:02:44", cb_city: "Dublin", cb_state_code: nil, cb_country_code: "IRL", old_city: nil, region_code: nil, is_hq: nil, cb_region: "Dublin", cb_updated: "2014-04-20 10:53:24", city_uuid: "97c70aa17568ca5375122f181f0484a7", city_path: "location/dublin/97c70aa17568ca5375122f181f0484a7">]
Don't What's wrong with query? Anyone knows what's wrong?
As pointed out by #Mischa and #Victor, the problem is indeed "NULL =". To solve the problem change the "query" parameter as below:
query = "(description = ? AND (address1 = ? OR address1 is NULL)) AND (city = (?) OR old_city = (?) OR cb_city = (?))"
No change is required to "office_data" parameter or your query.
Similarly, you may also need to make a change for city and other parameters too. As these can also have a NULL value
The problem is comparing address with NULL using the = sign - every operation besides IS against NULL is false. Because of that you end up with (true AND false) and (...), resulting in false.
Note that the second query you added in the comments contains no address = NULL - that's why it works in that case.

mongoid 'exists' don't work as expected

Hi am trying out a query and is giving me strange results. I am trying to get a list of object where a field doesn't exist with a where clause added to it.
Step one the where clause:
ContentMirror.where(source: 'some_source').count
This query returns 9984 records.
some_source have a field call vid_emded_obj which I know some are nil For example:
ContentMirror.find('50fff286781200986e000ae3')
=> #<ContentMirror _id: 50fff286781200986e000ae3, _type: nil, created_at: 2012-12-15 13:12:22 UTC, updated_at: 2013-01-29 12:10:23 UTC, deleted_at: nil, title: "Introduction to Polynomials", vid_emded_obj: nil, media_type: "video", source: "some_source", thumbnail_url: nil, md5: "459173975a7fb145b3ca8b99e1c2ae78">
So I was expecting that at lest that count of 1 will return if I do this:
ContentMirror.where(source: 'some_source').exists(vid_emded_obj: false).count
=> 0
Cannot work out why...
I belkieve that exists checks if the property is there at all, and will return true if the value of that property is empty.
I will just go hunt for the doc to confirm :)
EDIT:
So, the doc does not mention any exists(...).
Only an exists? method that can be applied on a criteria.
I think you should do (pseudo code, I haven't tried it)
ContentMirror.count(vid_embed_obj:nil, source: 'some_source')
or
ContentMirror.where(vid_embed_obj:nil, source: 'some_source').count
or again
ContentMirror.excludes(vid_embed_obj:nil).where(source: 'some_source').count

What kind of hash/data does acts_as_taggable_on output, and how can I extract a list of tag ids?

I'm using acts_as_taggable_on in an app and I would like to extract the tag ids (not the tag names) that a post has been tagged with.
My app has a posts controller, and in ruby console I can do:
>> post = Post.find(1)
=> #<Post id: 1, content: "Aliquam cupiditate ea deserunt et id placeat molest...", user_id: 1, created_at: "2011-07-06 19:29:44", updated_at: "2011-07-06 19:29:44">
>> tags = post.tag_counts_on("topics")
=> [#<ActsAsTaggableOn::Tag id: 1, name: "Politics">, #<ActsAsTaggableOn::Tag id: 2, name: "Economics">]
Here I have shown that the post is tagged with topic ids "Politics" and "Economics". My problem is, I want to save this information on a cookie for use later. But I can't store a hash on a cookie, I can only store strings of information. If I do:
session[:store_name] = tags.join(",")
And then later:
tags = session[:store_name].split(",")
I will get a hash:
["Politics", "Economics", ...]
But this hash doesn't have a record of the tag_id for each topic tag. Is there any way to pull the ids out at some point and save them for later with the acts_as_taggable_on output? Or some suggestions on how to preserve the output from acts_as_taggable_on for later use?
If you need the id's, just do
tags.map(&:id).join(",")