Part of a create method in my controller is:
if #organization.relationships && #organization.relationships.where('member = ?', true).any?
#organization.users.where('member = ?', true).each do |single|
single.create_digest
debugger
end
end
As you can see I've been testing with the debugger. In the debugger I'm experiencing the following strange behaviour. single and organization.users both display the details/values of the same user. However these values differ between when I examine using single and when I use organization.users in the debugger. For single the user does have values for activation_digest and activation_sent_at, while they are nil when I look at organization.users.
Can anyone explain this behaviour? The nil values are a problem since single isn't available outside the if statement. It's not clear to me whether the value have or have not been saved to the db.
P.S. The model method being used:
def create_digest
create_activation_digest
update_attribute(:activation_digest, self.activation_digest)
update_columns(activation_sent_at: Time.zone.now)
end
When you query for models in Rails (users in your case), each distinct query gives back separate copies of those models.
So for example, the users returned by this query:
#organization.users.where('member = ?', true)
Will be separate copies of the users that are returned by this slightly different query (I assume this is what you run in the debugger):
#organization.users
If you modify one copy of the user and save the modifications to the database, it will not automatically propagate those modifications to the other copy of the user. The other copy still has the (now out of date) data that was returned when you first ran the query.
To verify that the changes were actually persisted to the database, you can force Rails to refresh the user object with the latest data from the database by calling reload. For example:
# These are two different in-memory copies of the same user
user = #organization.users.where('member = ?', true).first
user_copy = User.find(user.id)
user.create_digest
# The copy is now out of date
user_copy.activation_sent_at # => nil
# Refresh the copy from the database
user_copy.reload
user_copy.activation_sent_at # => 2015-08-02 21:00:50 -0700
Related
Hello I was trying the update the data in the table using the rails console.
Box.where("code = 'learning'").update(duration: 10)
I ran this command.
The data is temporarily changing.
Box.where("code = 'learning'")
When I run this the precious data is being displayed.
Could anyone let me the issue.
Thank you in advance.
#update updates a single record.
user = User.find_by(name: 'David')
user.update(name: 'Dave')
It will return true/false depending on if the record was actually updated. You can see the validation errors by inspecting the errors object:
user.errors.full_messages
In non user-interactions situations like seed files and the console it can be helpful to use the bang methods such as #update!, #save! and #create! which will raise an exception if the record is invalid.
If you want to update multiple records at once you need to use #update_all:
Box.where("code = 'learning'")
.update_all(duration: 10)
This creates a single SQL update statement and is by far the most performant option.
You can also iterate through the records:
Box.where("code = 'learning'").find_each do |box|
box.update(duration: 10)
end
This is sometimes necissary if the value you are updating must be calculated in the application. But it is much slower as it creates N+1 database queries.
For demo purposes, suppose that I have a class called DemoThing with a method called do_something.
Is there a way that (in code) I can check the number of times that do_something hits the database? Is there a way that I can "spy" on active record to count the number of times that the database was called?
For instance:
class DemoThing
def do_something
retVal = []
5.times do |i|
retVal << MyActiveRecordModel.where(:id => i)
end
retVal
end
end
dt = DemoThing.new
stuff = dt.do_something # want to assert that do_something hit the database 5 times
ActiveRecord should be logging each query in STDOUT.
But for the above code, it's pretty obvious that you're making 5 calls for each iteration of i.
Queries can be made more efficient by not mixing Ruby logic with querying.
In this example, gettings the ids before querying will mean that the query isn't called for each Ruby loop.
ids = 5.times.to_a
retVal = MyActiveRecordModel.where(id: ids) # .to_a if retVal needs to be an Array
Sure is. But first you must understand Rails' Query Cache and logger. By default, Rails will attempt to optimize performance by turning on a simple query cache. It is a hash stored on the current thread (one for every active database connection - Most rails processes will have just one ). Whenever a select statement is made (like find or where etc.), the corresponding result set is stored in a hash with the SQL that was used to query them as the key. You'll notice when you run the above method your log will show Model Load statement and then a CACHE statement. Your database was only queried one time, with the other 4 being loaded via cache. Watch your server logs as you run that query.
I found a gem for queries count https://github.com/comboy/sql_queries_count
I'm writing an application that allows users to send one another messages about an 'offer'.
I thought I'd save myself some work and use the Mailboxer gem.
I'm following a test driven development approach with RSpec. I'm writing a test that should ensure that only one Conversation is allowed per offer. An offer belongs_to two different users (the user that made the offer, and the user that received the offer).
Here is my failing test:
describe "after a message is sent to the same user twice" do
before do
2.times { sending_user.message_user_regarding_offer! offer, receiving_user, random_string }
end
specify { sending_user.mailbox.conversations.count.should == 1 }
end
So before the test runs a user sending_user sends a message to the receiving_user twice. The message_user_regarding_offer! looks like this:
def message_user_regarding_offer! offer, receiver, body
conversation = offer.conversation
if conversation.nil?
self.send_message(receiver, body, offer.conversation_subject)
else
self.reply_to_conversation(conversation, body)
# I put a binding.pry here to examine in console
end
offer.create_activity key: PublicActivityKeys.message_received, owner: self, recipient: receiver
end
On the first iteration in the test (when the first message is sent) the conversation variable is nil therefore a message is sent and a conversation is created between the two users.
On the second iteration the conversation created in the first iteration is returned and the user replies to that conversation, but a new conversation isn't created.
This all works, but the test fails and I cannot understand why!
When I place a pry binding in the code in the location specified above I can examine what is going on... now riddle me this:
self.mailbox.conversations[0] returns a Conversation instance
self.mailbox.conversations[1] returns nil
self.mailbox.conversations clearly shows a collection containing ONE object.
self.mailbox.conversations.count returns 2?!
What is going on there? the count method is incorrect and my test is failing...
What am I missing? Or is this a bug?!
EDIT
offer.conversation looks like this:
def conversation
Conversation.where({subject: conversation_subject}).last
end
and offer.conversation_subject:
def conversation_subject
"offer-#{self.id}"
end
EDIT 2 - Showing the first and second iteration in pry
Also...
Conversation.all.count returns 1!
and:
Conversation.all == self.mailbox.conversations returns true
and
Conversation.all.count == self.mailbox.conversations.count returns false
How can that be if the arrays are equal? I don't know what's going on here, blown hours on this now. Think it's a bug?!
EDIT 3
From the source of the Mailboxer gem...
def conversations(options = {})
conv = Conversation.participant(#messageable)
if options[:mailbox_type].present?
case options[:mailbox_type]
when 'inbox'
conv = Conversation.inbox(#messageable)
when 'sentbox'
conv = Conversation.sentbox(#messageable)
when 'trash'
conv = Conversation.trash(#messageable)
when 'not_trash'
conv = Conversation.not_trash(#messageable)
end
end
if (options.has_key?(:read) && options[:read]==false) || (options.has_key?(:unread) && options[:unread]==true)
conv = conv.unread(#messageable)
end
conv
end
The reply_to_convesation code is available here -> http://rubydoc.info/gems/mailboxer/frames.
Just can't see what I'm doing wrong! Might rework my tests to get around this. Or ditch the gem and write my own.
see this Rails 3: Difference between Relation.count and Relation.all.count
In short Rails ignores the select columns (if more than one) when you apply count to the query. This is because
SQL's COUNT allows only one or less columns as parameters.
From Mailbox code
scope :participant, lambda {|participant|
select('DISTINCT conversations.*').
where('notifications.type'=> Message.name).
order("conversations.updated_at DESC").
joins(:receipts).merge(Receipt.recipient(participant))
}
self.mailbox.conversations.count ignores the select('DISTINCT conversations.*') and counts the join table with receipts, essentially counting number of receipts with duplicate conversations in it.
On the other hand, self.mailbox.conversations.all.count first gets the records applying the select, which gets unique conversations and then counts it.
self.mailbox.conversations.all == self.mailbox.conversations since both of them query the db with the select.
To solve your problem you can use sending_user.mailbox.conversations.all.count or sending_user.mailbox.conversations.group('conversations.id').length
I have tended to use the size method in my code. As per the ActiveRecord code, size will use a cached count if available and also returns the correct number when models have been created through relations and have not yet been saved.
# File activerecord/lib/active_record/relation.rb, line 228
def size
loaded? ? #records.length : count
end
There is a blog on this here.
In Ruby, #length and #size are synonyms and both do the same thing: they tell you how many elements are in an array or hash. Technically #length is the method and #size is an alias to it.
In ActiveRecord, there are several ways to find out how many records are in an association, and there are some subtle differences in how they work.
post.comments.count - Determine the number of elements with an SQL COUNT query. You can also specify conditions to count only a subset of the associated elements (e.g. :conditions => {:author_name => "josh"}). If you set up a counter cache on the association, #count will return that cached value instead of executing a new query.
post.comments.length - This always loads the contents of the association into memory, then returns the number of elements loaded. Note that this won't force an update if the association had been previously loaded and then new comments were created through another way (e.g. Comment.create(...) instead of post.comments.create(...)).
post.comments.size - This works as a combination of the two previous options. If the collection has already been loaded, it will return its length just like calling #length. If it hasn't been loaded yet, it's like calling #count.
It is also worth mentioning to be careful if you are not creating models through associations, as the related model will not necessarily have those instances in its association proxy/collection.
# do this
mailbox.conversations.build(attrs)
# or this
mailbox.conversations << Conversation.new(attrs)
# or this
mailbox.conversations.create(attrs)
# or this
mailbox.conversations.create!(attrs)
# NOT this
Conversation.new(mailbox_id: some_id, ....)
I don't know if this explains what's going on, but the ActiveRecord count method queries the database for the number of records stored. The length of the Relation could be different, as discussed in http://archive.railsforum.com/viewtopic.php?id=6255, although in that example, the number of records in the database was less than the number of items in the Rails data structure.
Try
self.mailbox.conversations.reload; self.mailbox.conversations.count
or perhaps
self.mailbox.reload; self.mailbox.conversations.count
or, if neither of those work, just try reloading as many of the objects as possible to see if you can get it to work (self, mailbox, conversations, etc.).
My guess is that something is messed up between memory and the DB. This is definitely a really weird error though, might wanna put in an issue on Rails to see why this would be the case.
The result of mailbox.conversations is cached after the first call. To reload it write mailbox.conversations(true)
I'm probably missing something very simple here, but can't understand what.
I'm trying to cache a simple active record query but every time I touch the cache, it runs the query against the DB again.
Controller Code:
products = Rails.cache.read("search_results")
if products
render :text => products[0].id
else
products = Product.where('name LIKE ?", 'product_name?')
Rails.cache.write("search_results", products)
end
I can see that in my second call I get to the if block and not the else, but any time I'm trying to touch products (like rendering it) I also see an active record call to the DB.
What am I missing?
The line
products = Product.where('name LIKE ?", 'product_name?')
returns an ActiveRecord::Relation, but does not hit the database unless a kicker method is called on it.
While I would still recommend using fetch as mentioned in my comment above, try changing the line to:
products = Product.where('name LIKE ?", 'product_name?').all
which will force the database hit, and save the actual results of the query into the cache, instead of the relation.
I am caching several User objects using Rails.cache.write but when I retrieve it from cache and try to call an attribute method (id, name, email, etc.) on any of the User objects I am getting
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.include?
I can call inspect on the User objects and I see all the attributes. Any ideas? Thanks
I'm using the standard cache configuration, no modification. Here is some code:
I am writing to the cache in my model inside an instance method that gets called when the user logs in like so:
cached_friends = self.friends
Rails.cache.write(self.id.to_s+"_friends", cached_friends)
Then in my controller I have a before filter:
def get_friends_from_cache
if current_user
friends = Rails.cache.read(current_user.id.to_s+"_friends")
friends.each do |friend|
if !friend.facebook_id.nil?
#other stuff....
end
end
end
end
I get the error on the if !friend.facebook_id.nil? line. facebook_id is a column in the db. Basically there are no User instance methods available for each User object. But I know the objects are there because I can insert raise "#{friends.first.inspect}" and everything spits out as I would expect.
This happens in development mode (i.e. with the setting config.cache_classes = false). The root cause:
ActiveRecord strips the attribute accessor methods from the class objects
MemoryStore (i.e. the default cache store) does not martial/unmartial objects
One workaround is to use MemCacheStore another one is to set config.cache_classes = true. See also Comments on railscasts