Rails update multiple records find based on other id - ruby-on-rails

Using Rails 3.2. As shown in the doc on update method, the update finds based on id:
update(id, attributes)
# id - This should be the id or an array of ids to be updated.
# Updates multiple records
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)
What if I want to update an array found based on other columns? For example:
people = { 'cook' => { "first_name" => "David" }, 'server' => { "first_name" => "Jeremy" } }
Find people with role = cook, then update first_name = David; find people with role = server, then update first_name = jeremy.
I want it to be done in 1 query if possible, and not by SQL. Thanks.

You can Achieve this with #update_all
people = { 'cook' => { "first_name" => "David" }, 'server' => { "first_name" => "Jeremy" } }
Person.update_all(people.keys, people.values)

In that case I would write my own sql statement. I depends on which database backend you are using.
http://www.postgresql.org/docs/9.1/static/plpgsql-control-structures.html
https://dev.mysql.com/doc/refman/5.0/en/case.html

The update method doesn't execute 1 SQL query when passed an array of ids and values. If you view the source code for update, you will see it loops through the array and executes 2 queries for each record (a find and an update query) to then return an array of updated objects.
If you're happy accepting that you will need to make 2 queries per row, then you can use the following code for finding people by role.
people = { 'cook' => { "first_name" => "David" }, 'server' => { "first_name" => "Jeremy" } }
updated_people = people.keys.map.with_index do |role, index|
object = Person.where(role: role).first!
object.update(people.values[index])
object
end
Note: This code only updates the first record it finds per role because I've assumed there will only be one cook with the first name 'David'.
If you want to only use 1 SQL statement, you should look at doing it in SQL like devanand suggested.

Related

Selecting columns from eager-loaded laravel models

I'm trying to select columns from related models, but I'm not quite sure how to do it using Eloquent (I want to avoid the DB namespace).
This is what I have so far...
$users = User::with("role", "country") -> select(["first_name", "last_name", "email_address"]);
How would I go about modifying the select list so that it also includes role.title and country.name?
So essentially, my select list would look something like this:
-> select(["first_name", "last_name", "email_address", "role.title", "country.name"]);
Many thanks!
Did you try
User::with(['role' => function ($query) {
$query->select('title');
}])->with(['country' => function ($query) {
$query->select('name');
}])->get();
or
User::with([
'role' => function ($query) {
$query->select('title');
},
'country' => function ($query) {
$query->select('name');
}
])->get();

Best way to join two different model queries in Rails?

I have two different models that I need to join together in a selection query and list on a page. They share all of attributes that I'll need to reference in the view (created_at, updated_at, name, etc), and I want them in order of creation. I'm wondering what the most efficient way to do this is? I was thinking of performing the selection query on each object individually and adding the relevant parts into a common array but that seems inefficient.
For example if my models were Dogs and Cats and I wanted a list of all dogs and cats of age 5, I was thinking something like
#pets = []
dogs = Dogs.where(:age => 5)
cats = Cats.where(:age => 5)
dogs.each do |dog|
hash = {"id" => dog.id, "name" => dog.name, "created_at" => dog.created_at }
#pets.push(hash)
end
cats.each do |cat|
hash = {"id" => cat.id, "name" => cat.name, "created_at" => cat.created_at }
#pets.push(hash)
end
But is that the best way to do it? also, I'm not sure how to sort the finished array in this example according to date created...
try this
dogs = Dogs.where(:age => 5)
cats = Cats.where(:age => 5)
#pets = (dogs + cats).sort_by { |pet| pet.created_at }
OR if you want your hashes still, use map to create the array of hashes
dogs = Dogs.where(:age => 5)
cats = Cats.where(:age => 5)
#pets = (dogs + cats).sort_by do |pet|
pet.created_at
end.map do |pet|
{ "id" => dog.id, "name" => pet.name, "created_at" => pet.created_at }
end

Mongoid Group By or MongoDb group by in rails

I have a mongo table that has statistical data like the following....
course_id
status which is a string, played or completed
and timestamp information using Mongoid's Timestamping feature
so my class is as follows...
class Statistic
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
field :course_id, type: Integer
field :status, type: String # currently this is either play or complete
I want to get a daily count of total # of plays for a course. So for example...
8/1/12 had 2 plays, 8/2/12 had 6 plays. Etc. I would therefore be using the created_at timestamp field, with course_id and action. The issue is I don't see a group by method in Mongoid. I believe mongodb has one now, but I'm unsure of how that would be done in rails 3.
I could run through the table using each, and hack together some map or hash in rails with incrementation, but what if the course has 1 million views, retrieving and iterating over a million records could be messy. Is there a clean way to do this?
As mentioned in comments you can use map/reduce for this purpose. So you could define the following method in your model ( http://mongoid.org/en/mongoid/docs/querying.html#map_reduce )
def self.today
map = %Q{
function() {
emit(this.course_id, {count: 1})
}
}
reduce = %Q{
function(key, values) {
var result = {count: 0};
values.forEach(function(value) {
result.count += value.count;
});
return result;
}
}
self.where(:created_at.gt => Date.today, status: "played").
map_reduce(map, reduce).out(inline: true)
end
which would result in following result:
[{"_id"=>1.0, "value"=>{"count"=>2.0}}, {"_id"=>2.0, "value"=>{"count"=>1.0}}]
where _id is the course_id and count is the number of plays.
There is also dedicated group method in MongoDB but I am not sure how to get to the bare mongodb collection in Mongoid 3. I did not have a chance to dive into code that much yet.
You may wonder why I emit a document {count: 1} as it does not matter that much and I could have just emitted empty document or anything and then always add 1 to the result.count for every value. The thing is that reduce is not called if only one emit has been done for particular key (in my example course_id has been played only once) so it is better to emit documents in the same format as result.
Using Mongoid
stages = [{
"$group" => { "_id" => { "date_column_name"=>"$created_at" }},
"plays_count" => { "$sum" => 1 }
}]
#array_of_objects = ModelName.collection.aggregate(stages, {:allow_disk_use => true})
OR
stages = [{
"$group" => {
"_id" => {
"year" => { "$year" => "$created_at" },
"month" => { "$month" => "$created_at" },
"day" => { "$dayOfMonth" => "$created_at" }
}
},
"plays_count" => { "$sum" => 1 }
}]
#array_of_objects = ModelName.collection.aggregate(stages, {:allow_disk_use => true})
Follow the links below to group by using mongoid
https://taimoorchangaizpucitian.wordpress.com/2016/01/08/mongoid-group-by-query/
https://docs.mongodb.org/v3.0/reference/operator/aggregation/group/

Multiple LIKE matches with Arel & MetaWhere

I'm transitioning an application written in Rails-2.3 with SearchLogic to Rails-3.0 with Arel and MetaWhere, and I'm running into an operation that I don't know how the write.
The old code was:
if params[:city] && params[:city].respond_to?(:each)
users = users.person_address_city_like_any(params[:city])
end
what this did was run a LIKE match against each item in the params[:city] array.
This is easy enough in MetaWhere when there's only one search term:
users = users.where(:person => { :address => { :city.matches => '%city1%' } })
but how would I write this with an arbitrary number of cities?
Try:
users = users.where(:person => { :address => { :city.matches_any => ['%city1%','%city2%'] } })

Making an if statement inside a hash in a model

i am trying to display all the residents on a pdf and their dependents, but the dependents doesn't have a stand they are identified by the foreign key user_id of the resident. e.g resident1.id = 5 --- dependent3.user_id = 5 mean dependent3 belongs to resident1, and therefore if i want to display the dependent stand what should i do and i would like to display all the residents and their dependents and the stand information for dependents to be the stand info of the resident the dependent belong to. now my information should be inside a hash so it can generate my pdf file..
my code is
data = []
c = 1
residents.each do |r|
data <<{"Fullname" => r.firstname, "Lastname" => r.lastname, "Street-Number" => r.stand.street_no, "street Name" => r.stand.streetname} if r.stand || r.user_id = r.id
end
remember my dependents and residents are in the same table but residents doesn't have the user_id foreigh key only the dependent have it.
and my output only display the information of the residents who have stands not the dependents.
please anyone who is willing to help.cause i dont know if i can but an if statement inside a hash like:
residents.each do |r|
data <<{"Fullname" => r.firstname, "Lastname" => r.lastname} if r.stand || r.user_id = r.id{"Street-Number" => r.stand.street_no, "street Name" => r.stand.streetname}
Assuming that .stand.street_no, etc. is valid even if .stand is false (since it could be false and still be called if the r.user_id == r.id part is true), then the following would work:
data = residents.map do |r|
hash = { "Fullname" => r.firstname, "Lastname" => r.lastname }
hash.merge!({ "Street-Number" => r.stand.street_no, "street Name" => r.stand.streetname}) if r.stand || r.user_id == r.id
hash
end
Explanation:
Iterate through each resident object and build a hash that contains values that we know will be used.
Conditionally merge! in the extra keys if the if clause is true.
Return the hash.
Doing this in a map eliminates the need to predeclare the data array, nor push the hash on each pass through the loop.
data = []
residents.each do |r|
tmp_hash = {"Fullname" => r.firstname, "Lastname" => r.lastname}
if r.stand || r.user_id = r.id
tmp_hash.merge({"Street-Number" => r.stand.street_no, "street Name" => r.stand.streetname})
end
data << tmp_hash
end

Resources