Check if the update value differs from database stored one - ruby-on-rails

I'm developing an event calendar, where each hour is a slot that allows users to sign one appointment.
Since there is a feature that allows users to update the slot (and also change date/time) I'd like to perform some check before to update the value, making sure that the slot is free.
So I ended up with a method like this:
if Event.find(:all, :conditions => ["start_at = ? AND event_calendar_id = ?", self.start_at, self.id])
errors.add :base, "You cannot save this appointment in that slot."
return false
end
By the way it creates issues when updating other fields without changing the datetime field, because it finds itself and raises the exception making impossible to update it.
Is there a way I can access database data such as the current id so I can filter out itself from the values, or check if the submitted datetime field is equal to the database one (so i can skip this check).
What's the best way to do this?
Thanks.
P.S. I'm using rails 3.2.3

To exclude self from the results just add a condition to your where excluding its id. Also save some memory and processor time by calling exists?, which just returns true or false instead of fetching an entire row and building a new object:
Event.where( :start_at => start_at,
:event_calendar_id => event_calendar.id ).
where( "id <> ?", id ).
exists?

Related

Making posts inactive after a date and time

I've to make posts inactive after a given date and time by the user who creates these posts. I've added a boolean field "published" to make a post active or unactive.
If the post is active, every visitor will see the post. If it's inactive, the post will be seen in the user dashboard as inactive.
For you guys what is the best way to make a post inactive after the given datetime passed? That should be automatic but I don't think the suitable way is using Active Job and ;
Check all posts in the db if active,
If active, compare the given datetime and current datetime
If needed, make it inactive
I'm using heroku, so that'll make my worker busy all the time.
If you simply plan to fetch and display posts, rather than setting the boolean value, you could just add a scope to the model:
scope :published, -> { where('publish_at < ?', Time.now) }
And use like this Post.published
Let me know if this helps.
Update 1:
If you need to determine if a post is active or not, add a method to the model like this,
def active?
self.publish_at < Time.now
end
You can do some things:
Option 1:
Based in the #jawad-khawaja answer, I think a better approach is to use expired_at instead of published_at because for me, a publish date is a date when a post is available for people. This way the user can define an expiration date for his own post (not the same publish date).
Here some example how to you can get active/inactive posts based on its expiration date
# active posts
scope :published, -> { where('expired_at > ?', Time.now) }
# inactive posts
scope :unpublished, -> { where('expired_at < ?', Time.now) }
Option 2:
If you have an attribute published as boolean and you really need mark it automatically, the option is to use whenever gem
CONS for this option:
You will have two attributes representing the same thing: published as boolean and expiration_datetime as datetime
If the minimum unit for an expiration date is a minute, you need to check every minute if every not-expired post enter to the expired state. And you will have probably an overload for your server.
Conclusion:
My recommended way is to choose the option 1.

Rails: destroy all but newest based on column

I've got an Authorisation model that has outdated duplicates in it. I want to delete all except the newest based on a :provider column in Authorisation, leaving me with the most recent "linkedin" Authorisation, the most recent "facebook" etc, etc.
How would I write that method? Something like this logically but it gives this error (TypeError: can't convert Authorisation to Array (Authorisation#to_ary gives NilClass)):
old = Authorisation.where(provider: 'facebook') - Authorisation.where(provider: 'facebook').last
Maybe this one:
last_record = Authorisation.where(provider: 'facebook').last
Authorisation.where('created_at < ?', last_record.created_at).delete_all
If there is created_at. In other case you should get all ids except last, and remove records with id from that array.
Another way, is apply not to query. Like:
Authorisation.where.not(id: Authorisation.last.id).delete_all
But it work for Rails 4 only, i think.
Update
This is better:
Authorisation.where('id != ?', Authorisation.last.id).delete_all

Ruby on Rails - ActiveRecord::Relation count method is wrong?

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)

Rails cross model validation

I have two tables one for members and the other for employees, both have an attribute called id_number this attribute is not required and can be null.
Is it possible to run a validation to ensure the uniqueness of the id_number, so that if an employee is added with the same id_number as an member or vice versa that it will give an error.
I am thinking of writing my own validation but hitting the db for each instance will be very slow as some companies upload 10's of thousands of employees at a time.
Yes that's possible with your own validation. I think you have to hit the database, otherwise you never could check if it exists already.
def your_validation
employee_ids = Employee.all.map(&:id_number)
member_ids = Member.all.map(&:id_number)
id = self.id_number
if employee_ids.include?(id) || member_ids.include?(id)
errors.add(:id_number, "is already taken")
end
end
I think adding an index to your id_number will be good.
UPDATE: The above method could be changed to following to improve the performance:
def your_validation
employee_ids = Employee.all.map(&:id_number)
if employee_ids.include?(self.id_number)
errors.add(:id_number, "is already taken")
else
member_ids = Member.all.map(&:id_number)
if member_ids.include?(self.id_number)
errors.add(:id_number, "is already taken")
end
end
end
The first one is cleaner, the second one should be faster. But check this out with a lot of db entries and a benchmark tool.
I think you'll want something like this:
def your_validation
if self.id_number.present?
if Employee.exists?(:id_number=>self.id_number) || Member.exists(:id_number=>self.id_number)
errors.add(:id_number, "is already taken")
end
end
end
if you have indices on the id_number columns this check should run very quickly and is the same check that validates_uniqueness_of would use within a single table. Solutions that involves fetching all ids into rails will start running into problems when the tables get large.
Another thing to note is that if your app runs multiple web server instances at a time these kinds of rails side checks can't 100% guarantee uniqueness as they are subject to races between threads. The only way to ensure uniqueness in such situations would be to use facilities built into your database or generate the id_numbers yourself from a source that precludes duplicates (such as a database sequence).

Count current users on the page

I'm trying to count current viewers on the particular page. I need this count to be stored in the DB. The main trouble is to clean up after user leaves the page.
Users are anonymous. Every active user sends AJAX-request every 5 seconds.
What's the best algorithm to do that? Any suggestions?
UPD: I'm trying to reduce amount of queries to the DB, so, I think, I don't really need to store that count in the DB while I can access it other way from the code.
Don't even think about storing this in database, your app will be incredibly slowed down.
So use Cache for this kind of operation.
To count the number of people, I'd say:
assign a random ID to each anonymous user and store it in his session
send the ID within your ajax call
store an Array of Hashes in cache with [{ :user_id, :latest_ping }, {} ] (create a cache var for each page)
delete the elements of the array which appear to be too old
you've your solution: number of users = nb of elements in the array
If you store the users in the database somehow, you could store a last_seen_at field in the users table, and update that with Time.now for every AJAX request that user sends.
To display how many users you currently have, you can just perform a query such as:
#user_count = User.where("last_seen_at < ?", 5.seconds.ago).count
If you want to clean up old users, I suggest that you run some kind of cron job, or use the whenever gem, or something like that, to periodically delete all users that haven't been seen for some time.
I would suggest you create a model that contains a unique key (cookie-id or something) that you save or update with every AJAX heartbeat request.
You then have a session controller that could look like this:
def create
ActiveUser.where(:cookie => params[:id]) || ActiveUser.new
ActiveUser.cookie = prams[:id]
ActiveUser.timestamp = Time.now
ActiveUser.save
end
Your number of active users is then simply a SELECT COUNT(*) FROM ActiveUsers WHERE timestamp > NOW() - 5 or something like that.
Martin Frost is on the right track. There's the #touch method to update last_seen_at: user.touch(:last_seen_at)
But it would be even more efficient to just update the user without having to fetch the model from the database:
> User.update_all({:last_seen_at => Time.now}, {:id => params[:user_id})
SQL (3.1ms) UPDATE "users" SET "last_seen_at" = '2011-11-17 12:37:46.863660' WHERE "users"."id" = 27
=> 1

Resources