I have a table A(:name, :address, :phone) and want to update the names and addresses of all the records. The following lines of code helps me doing it but takes a lot of time :
A.find_each(batch_size: 10000) do |a|
new_name = "xyz"
new_address = "abc"
A.find(a.id).update(name: name, address: new_address)
end
It takes times because of the last line as I am finding the record first, then updating it. Now when I do this :
A.find_each(batch_size: 10000) do |a|
a.name = "xyz"
a.address = "abc"
a.save
end
The names and addresses don't get updated in this case but a.save returns true. What could be wrong?
Can you try printing out the errors while saving if any:
A.find_each(batch_size: 10000) do |a|
a.name = "xyz"
a.address = "abc"
a.save
puts a.errors.full_messages
end
Try to do this like:
A.find_each(batch_size: 10000){ |a| a.update_attributes(name: 'name', address: 'address', phone: 23213) }
If you want all records to be updated with same value, the below solution will be fast.
A.update_all(name: 'xyz', address: 'abc')
You can fire direct SQL query and update directly using single SQL statement
A.find_by_sql "UPDATE table SET name = 'abc' and address = 'xyz'";
A.update_all(name: 'xyz', address: 'abc')
Related
Each user has one address.
class User
include Mongoid::Document
has_one :address
end
class Address
include Mongoid::Document
belongs_to :user
field :street_name, type:String
end
u = User.find(...)
u.address.update(street_name: 'Main St')
If we have a User without an Address, this will fail.
So, is there a good (built-in) way to do u.address.update_or_initialize_with?
Mongoid 5
I am not familiar with ruby. But I think I understand the problem. Your schema might looks like this.
user = {
_id : user1234,
address: address789
}
address = {
_id: address789,
street_name: ""
user: user1234
}
//in mongodb(javascript), you can get/update address of user this way
u = User.find({_id: user1234})
u.address //address789
db.address.update({user: u.address}, {street_name: "new_street name"})
//but since the address has not been created, the variable u does not even have property address.
u.address = undefined
Perhaps you can try to just create and attached it manually like this:
#create an address document, to get _id of this address
address = address.insert({street_name: "something"});
#link or attached it to u.address
u.update({address: address._id})
I had this problem recently. There is a built in way but it differs from active records' #find_or_initialize_by or #find_or_create_by method.
In my case, I needed to bulk insert records and update or create if not found, but I believe the same technique can be used even if you are not bulk inserting.
# returns an array of query hashes:
def update_command(users)
updates = []
users.each do |user|
updates << { 'q' => {'user_id' => user._id},
'u' => {'address' => 'address'},
'multi' => false,
'upsert' => true }
end
{ update: Address.collection_name.to_s, updates: updates, ordered: false }
end
def bulk_update(users)
client = Mongoid.default_client
command = bulk_command(users)
client.command command
client.close
end
since your not bulk updating, assuming you have a foreign key field called user_id in your Address collection. You might be able to:
Address.collection.update({ 'q' => {'user_id' => user._id},
'u' => {'address' => 'address'},
'multi' => false,
'upsert' => true }
which will match against the user_id, update the given fields when found (address in this case) or create a new one when not found.
For this to work, there is 1 last crucial step though.
You must add an index to your Address collection with a special flag.
The field you are querying on (user_id in this case)
must be indexed with a flag of either { unique: true }
or { sparse: true }. the unique flag will raise an error
if you have 2 or more nil user_id fields. The sparse option wont.
Use that if you think you may have nil values.
access your mongo db through the terminal
show dbs
use your_db_name
check if the addresses collection already has the index you are looking for
db.addresses.getIndexes()
if it already has an index on user_id, you may want to remove it
db.addresses.dropIndex( { user_id: 1} )
and create it again with the following flag:
db.addresses.createIndex( { user_id: 1}, { sparse: true } )
https://docs.mongodb.com/manual/reference/method/db.collection.update/
EDIT #1
There seems to have changes in Mongoid 5.. instead of User.collection.update you can use User.collection.update_one
https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/
The docs show you need a filter rather than a query as first argument but they seem to be the same..
Address.collection.update_one( { user_id: user_id },
'$set' => { "address": 'the_address', upsert: true} )
PS:
If you only write { "address": 'the_address' } as your update clause without including an update operator such as $set, the whole document will get overwritten rather than updating just the address field.
EDIT#2
About why you may want to index with unique or sparse
If you look at the upsert section in the link bellow, you will see:
To avoid multiple upserts, ensure that the filter fields are uniquely
indexed.
https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/
I've been trying to do this for the past 10 hours, but it's been useless.
For example:
Event.where(login_screen: Time.now-8.days ..Time.now)
I have an Event table and login_screen is one of the column names. I'm listing them in a drop-down menu and I'd like to take the event names as a variable. It's in the request params like this: params[:segmentation][:first_event]. When I tried to give it like:
Event.where(params[:segmentation][:first_event] Time.now-8.days ..Time.now)
...it didn't work. I tried to use to_sym but that didn't help either.
How can I use a variable as a symbol?
Another question:
What's the difference between :hello and hello: ?
It's alternative syntax for ruby hashes with symbols as keys
Event.where(login_screen: Time.now-8.days ..Time.now)
is the same as
Event.where(:login_screen => Time.now-8.days ..Time.now)
So, if you store key in variable you need use 'hash rocket' syntax:
Event.where(params[:segmentation][:first_event] => Time.now-8.days ..Time.now)
these are the different ways to pass arguments in where clause:--
User.where(["name = ? and email = ?", "Joe", "joe#example.com"])
User.where(["name = :name and email = :email", { name: "Joe", email: "joe#example.com" }])
User.where("name = :name and email = :email", { name: "Joe", email: "joe#example.com" })
using hash:-
User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
User.where({ name: ["Alice", "Bob"]})
User.where({ name: "Joe", email: "joe#example.com" })
I have an array of objects. I need to use an SQL-like condition WHERE field like '%value%' for some object fields in this array.
How to do it?
Edited:
For example I have array with Users and I need to find all users with first_name like ike and email like 123.
Edited2:
I need method to get Users with first_name like smth and email like smth from ARRAY of my Users. Users have first_name and email.
Edited3:
All my users are in database. But I have some business logic, at the end of this logic I have array with Users. Next I need to filter this array with some text: ike for first_name and 123 for email. How to do it?
arr = %w[hello quick bool boo foo]
arr.select { |x| x.include?("foo") }
=> ["bool", "boo", "foo"]
or in your case, if you have an array of objects, you can do:
x.first_name.include?("foo") && x.email.include?("123")
For more customization, you can use Array#select with Regexeps
If you can just use ruby methods for this that do something like this:
User = Struct.new(:email, :first_name) # Just creating a cheap User class here
users = [
User.new('1#a.com' , 'ike'),
User.new('123#a.com', 'bob'),
User.new('123#a.com', 'ike'),
]
# results will be an array holding only the last element in users
results = users.find_all do |user|
user.email =~ /123/ and
user.first_name =~ /ike/
end
Writing your own sql parser seems like a pretty bad idea, but if you really need to parse simple SQL where clauses you could do something like this:
User = Struct.new(:email, :first_name) # Just creating a cheap User class here
users = [
User.new('1#a.com' , 'ike'),
User.new('123#a.com', 'bob'),
User.new('123#a.com', 'ike'),
]
def where(array, sql)
sql = sql.gsub(/\s+AND\s+/, ' ') # remove AND's
terms = Hash[ *sql.split(/\s+LIKE\s+| /) ] # turn "a LIKE 'b'" into {'a': "'b'"}
array.find_all do |item|
terms.all? do |attribute, matcher|
matcher = matcher.gsub('%', '.*') # convert %
matcher = matcher.gsub(/^['"]|["']$/, '') # strip quotes
item.send(attribute) =~ /^#{matcher}$/
end
end
end
# results will be an array holding only the last element in users
results = where(users, "first_name LIKE '%ike%' AND email LIKE '%123%'")
This will only work for where clauses what only contain LIKE statements connected by AND's. Adding support for all valid SQL is left as an exercise for the reader, (or better yet, just left alone).
I've built a ruby gem uber_array to enable sql-like syntax for arrays of Hashes or Objects you might want to try.
require 'uber_array'
# Array of Hash elements with strings as keys
items = [
{ 'name' => 'Jack', 'score' => 999, 'active' => false },
{ 'name' => 'Jake', 'score' => 888, 'active' => true },
{ 'name' => 'John', 'score' => 777, 'active' => true }
]
uber_items = UberArray.new(items)
uber_items.where('name' => 'John')
uber_items.where('name' => /Ja/i)
uber_items.like('ja')
uber_items.where('name' => %w(Dave John Tom))
uber_items.where('score' => 999)
uber_items.where('score' => ->(s){s > 900})
uber_items.where('active' => true, 'score' => 800..900)
I am trying to do this:
query << ["AND f.name LIKE '%:last_name%' ", :last_name => params[:last_name] ]
But getting an error. Surely the syntax is incorrect.
Does anyone know how to do it?
Is this an array or an hash? the first item looks like an array and the second like a hash. Without some context it is hard to tell. You can start by maybe trying this:
["AND f.name LIKE '%:last_name%' ", {:last_name => params[:last_name]} ]
Where query is an array you may want to pass a string so you don't end up with an array of arrays, you could do this:
Rails 3
query << "AND f.name LIKE '%#{params[:last_name]}%' "
Rails 2
last_name = ActionController::Base.helpers.sanitize(params[:last_name])
query << "AND f.name LIKE '%#{last_name}%' "
since you're not using rails 3, you should use scoped to chain queries
records = Record.scoped(:conditions => { :active => true })
records = records.scoped(:conditions => ["records.name LIKE '%:last_name%' ", { :last_name => params[:last_name] }])
so you don't have to build queries like that.
In Rails, I have a class name User, in which I just want to look at :name, :address, :age
I would like to write a piece of code that's something like:
user = User.new
[name, address, age].zip(["Name", "Address", 10]).each do |attribute, val|
user.attribute = val
end
The thing is I don't know how to do it properly, since user.attribute is obviously not a valid line. In other word, is there anyway so that user.attribute gets evaluated as user.name, user.address, user.age depends on the loop?
You should use send method
user.send "#{attribute}=", val
If attribute is, say, :name, then the line above is equivalent to
user.name = val
Actually I can do it this way:
[name, address, age].zip(["Name", "Address", 10]).each do |attribute, val|
user[attribute] = val
end
This way works, too
user.send(:name) would be the same as calling user.name, so you may want to try that.
user = User.new
[name, address, age].zip(["Name", "Address", 10]).each do |attribute, val|
user.write_attribute(attribute, val)
end
But that's what I'd write:
user = User.new
user.attributes = {name => "Name", address => "Address", age => 10}