I have a website where I need to be able to display on each user's profile the last time they were "active" on the site. In this case, "active" is defined by browsing content, interacting with other users and completing courses.
My plan is to have a last_active column on the Users table, which I can update with Time.now. The question is, how do I do this without hitting up the database during every single controller action? That seems... expensive. For example, I want to avoid doing this:
# In each controller
def index
current_user.activity
end
# In the User model
def activity
self.update_attribute(:last_active, Time.now)
end
Because then every time a user gets the content listing, I have to make a database call.
The other option would be to have an Activity table, which I update with various user actions (kind of like audits). That would allow me to store and display more relevant information about what users are doing. But that goes back to the same question: how do I update these tables without massive overhead?
It's really a rather moot question – No, there is no way to update the database without updating the database.
If you wanted to get complicated, you could try to do some client-side scripting to store that information in a session variable or a cookie, and only commit it to the db once in a while, but that seems like a lot of work for a small feature.
Maybe if you add an index to your :last_active column you make it marginally less expensive? But otherwise, I would just go for it, and try to be conservative about how often it's updated.
You could also check if Time.now > #user.last_active+10.minutes before updating to make sure that you aren't constantly writing to the db, but then your just querying instead which may not be better...
I don't think there is a way around the overhead without restricting when you update a particular user's 'last active' attribute.
So as Charlie Egan alluded to, you have two options:
Only update the 'last active' attribute when the user logs in. You'll still get a decent sense of a user's general activity on the site just by doing this.
Or
Only update the 'last active' attribute on certain activity. For example, you mentioned in your question that users can complete courses. That seems like a fairly significant 'activity', so update the 'last active' attribute. Less significant activities, like browsing content can be ignored.
Don't you think about redis|memcache|any_in_memory_storage for such data?
If you're fine with async updates, you could set up a delayed resque job, deleting previous jobs.
Resque.remove_delayed_selection RecordLastUserActivity, {
|args| args[0]['user_id'] == current_user.id
}
Resque.enqueue_at(10.minutes.from_now, RecordLastUserActivity,
user_id: current_user.id, last_seen_at: Time.now)
Not sure if this will provide greater perfomance though, it will require some testing.
Related
long time reader first time poster.
I recently started using ruby on rails so I am still very new to the environment (even though I have completed a few guides) so be gentle please.
What I want to do is create a sort of archive table of another table that the user can access at any time(via a different link on the website).
So for example, if I have the "users" table, I want to be able to archive old users but still give the option for someone to go and view them.
Basically, it will sort of have to delete the user from the initial table, and save his/her info in to the archived_users table.
Thank you for your time.
I figured my comment was more of an answer, so posting it here and adding more info
In this situation you're better off adding some sort if "active" flag to the users table, which you can flip on or off as needed. That way you don't need to worry about dealing with yet another model class, and you can reuse all the same view and controller structures. In your views, you can then simply "hide" any inactive users (and maybe only show inactive folks if the logged in user is an admin...etc).
You also have the freedom to include other meta data such as "deactivated on" (time stamp) for example.
Long story short, if you're concerned about performance, with proper indexing (and partitioning if necessary), you shouldn't really need to create a separate archive table.
The only reason I can think of to do this is if you're dealing with billions upon billions of records, and/or growing by an insane amount (which is probably not your case).
The best way to do this is probably to add a column called deleted on the original Users table. You can then filter out the old users in normal circumstances (preferably using a default scope) but allow them to be seen/queried when needed.
Papertrail might work for you.
It creates a "versions" table and logs create/update/destroy events for any class which includes has_paper_trail. For example:
class User < ActiveRecord::Base
has_paper_trail
end
deleted_users = Papertrail::Version.where(item_type: User, event: "destroy")
deleted_users.last.reify.name # assuming the users table has a 'name' column
This is my first rails app and I am now trying to optimize and cache appropriately and it can be a little daunting at first.
Whenever Programs are listed, if the User is the creator of that Program it shows an "Edit Program" Button. Otherwise, nothing is displayed.
The program list will not change that often (maybe a couple times a day?) What is the best way to cache this?
It seems like it would be wrong to do a fragment cache for each user/program relationship, because it is just a single User who will see that button.
Or do I simply need to redesign my view to ignore this problem.
I would suggest doing a cache key that is based on both the program as well as a method like, program.owned_by(user).
For example, the view might be something like this
<% cache [program.cache_key, program.owned_by?(user)] do %>
# all of your view code here
<% end %>
What this will do is create fragment cache not only on the program but whether or not the thing is owned by the user object. program.cache_key will be based on the programs updated_at timestamp so it should expire when necessary. The second part of this compound cache key would essentially be true or false based on whether or not the program is owned by the user. You would have to implement that method in your model.
Hope this helps.
In my user model I have a friends method that returns the hash of all the user's facebook friends. In my view I iterate through the entire list to paginate that list alphabetically. I can't tell if my server is running really slow or if this is extremely inefficient. How can I make this process faster? Is it better to maybe create a friendsmodel? Please let me know if my method is inefficient, why, and how I might be able to make it faster. Thanks!
In my Home.html.erb view I have <%letter ='a'%> which changes when the user selects a different letter and the page refreshes.
<% current_user.friends.each do |user| %>
<% if user['name'].downcase.start_with? letter %>
do something
<% end %>
User Model
def facebook
#facebook ||= Koala::Facebook::API.new(token)
block_given? ? yield(#facebook) : #facebook
rescue Koala::Facebook::APIError => e
logger.info e.to_s
nil
end
def friends
facebook {|fb| fb.get_connections("me","friends")}.sort{|a,b| a['name']<=>b['name']}
end
You are making an external API call for every request. Plus user may have good number of friends like 500, 1000.
I in my fb app processing the data in background job(delayed job). You can use resque or sidekiq or some other background to process user data.
I would suggest you to make Friend model and have its association with users model. Then if you have some n+1 query problem you can use includes and instead of using sort use order it would be much faster then sort. Moreover instead of using each use find_each it will process the data in chunks you can google the difference between each and find_each. hope it would be helpful
One thing that will be slowing down each request for sure is the fact that your making an external API call in the middle of the request. The second thing to note is that your potentially bringing back a large amount of data, easily getting into the hundreds, if not thousands.
A more appropriate way to handle this would be to create a Friend model where each friend has a belongs to relationship to the User. In a background processor (ie delayed job, resque, sidekiq), iterate through your users and update their friends at some interval that your server can tolerate. It will cause some lag as to when the user's friends will show up. You'll have to be the judge as to how much lag time is tolerable, and it depends largely on your number of users and budget for hardware.
This is effectively a caching mechanism, and you may want to account for the fact that data will change, friends may be removed and so on. You could delete all the friends and recreate the whole list on each refresh. Doing so inside a transaction will keep the deletes from showing up until it is commited.
I have a requirement that certain attribute changes to records are not reflected in the user interface until those changes are approved. Further, if a change is made to an approved record, the user will be presented with the record as it exists before approval.
My first try...
was to go to a versioning plugin such as paper_trail, acts_as_audited, etc. and add an approved attribute to their version model. Doing so would not only give me the ability to 'rollback' through versions of the record, but also SHOULD allow me to differentiate between whether a version has been approved or not.
I have been working down this train of thought for awhile now, and the problem I keep running into is on the user side. That is, how do I query for a collection of approved records? I could (and tried) writing some helper methods that get a collection of records, and then loop over them to find an "approved" version of the record. My primary gripe with this is how quickly the number of database hits can grow. My next attempt was to do something as follows:
Version.
where(:item_type => MyModel.name, :approved => true).
group(:item_type).collect do |v|
# like the 'reify' method of paper_trail
v.some_method_that_converts_the_version_to_a_record
end
So assuming that the some_method... call doesn't hit the database, we kind of end up with the data we're interested in. The main problem I ran into with this method is I can't use this "finder" as a scope. That is, I can't append additional scopes to this lookup to narrow my results further. For example, my records may also have a cool scope that only shows records where :cool => true. Ideally, I would want to look up my records as MyModel.approved.cool, but here I guess I would have to get my collection of approved models and then loop over them for cool ones would would result in the very least in having a bunch of records initialized in memory for no reason.
My next try...
involved creating a special type of "pending record" that basically help "potential" changes to a record. So on the user end you would lookup whatever you wanted as you normally would. Whenever a pending record is apply!(ed) it would simply makes those changes to the actual record, and alls well... Except about 30 minutes into it I realize that it all breaks down if an "admin" wishes to go back and contribute more to his change before approving it. I guess my only option would be either to:
Force the admin to approve all changes before making additional ones (that won't go over well... nor should it).
Try to read the changes out of the "pending record" model and apply them to the existing record without saving. Something about this idea just doesn't quite sound "right".
I would love someone's input on this issue. I have been wrestling with it for some time, and I just can't seem to find the way that feels right. I like to live by the "if its hard to get your head around it, you're probably doing it wrong" mantra.
And this is kicking my tail...
How about, create an association:
class MyModel < AR::Base
belongs_to :my_model
has_one :new_version, :class_name => MyModel
# ...
end
When an edit is made, you basically clone the existing object to a new one. Associate the existing object and the new one, and set a has_edits attribute on the existing object, the pending_approval attribute on the new one.
How you treat the objects once the admin approves it depends on whether you have other associations that depend on the id of the original model.
In any case, you can reduce your queries to:
objects_pending_edits = MyModel.where("has_edits = true").all
then with any given one, you can access the new edits with obj.new_version. If you're really wanting to reduce database traffic, eager-load that association.
This might be a stupid questions but I wanted to know what happens if two users edit some data at once and then both click submit at the same time, I assumed Rails handled requests one after the other and so someone would get an error message but is this correct?
Thanks
Once one person has edited data I dont want it to be accessible or editable anymore, which is handled by validations
Ive got this validation in my model as well
def account_active
if self.active == true
return true
else
return false
end
end
Where active is a Boolean set within the controller if the validations pass
As has been mentioned in other answers, the latest write wins.
You might not think this is a problem but as there's no pessimistic lock preventing two users from having the same edit form open at once, both users may think the change they're making will be saved.
There is a way around this by using a version number or timestamp on your models that the system can use to tell "the user was editing version 1, but now there's version 2" and prevent the second user from overriding the first user's write.
Ryan Bates' awesome Railscasts series has covered the basics on how to set this up in Railscast 59: Optimistic Locking.
Your web server daemon would handle the requests one after the other; whichever request gets handled last becomes the newest update. Nobody would receive an error message unless you write some logic to handle such cases.
As with all race conditions involving blind writes, last one wins unless you take steps to change that.
Your original question was answered, but I'll add this:
For the validation, you can simply do the following
def account_active
self.active?
end
Ruby implicitly returns the last line of the method.