Rails caching with user specific button - ruby-on-rails

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.

Related

Make a copy / clone of an active object in ruby, and make changes to it, without messing with the database

I am new to ruby. I've coded in many languages, and normally get things quickly if there is a good reference and things are explained logically. I am going out of my mind. I've looked at every possible question related to this on stackoverflow, as well as on other websites. Everybody says use .clone or .dub or freeze and even something like Marshal.load(Marshal.dump(arr)) but none of those work.
I just want a copy of the original object, that I can modify at runtime, without it making any changes to the database.
The rails project loads a bunch of products into an object, which is then injected into a dropdown. Let's say it's called #products. The client wants me to remove certain entries from being displayed in the dropdown, but they must NOT be removed from the database.
In php for instance, you would just load the db object into a variable, and delete what you do not want by id for instance, and then loop through the resulting object / array and that creates the drop down. This makes NO alteration to the database.
I realized very quickly, Ruby does not work like that, and it deletes things direct from the database, even if I use .clone or .dup.
Let's say I use tempproducts = #products, and I do something like tempproducts.delete(11) , I don't want the system to go and delete #products(11) as well.
This is an elementary function of programming, why can't I figure out to do something as simple as this?
Thanks kindly to anybody that can help me out with this, or even post a link to the correct answer!
Checkout Array#select method to filter collection of records
e.g Let's say your Product model has one column shipping_category and you want to show only product with local and zonal shipping categories then it will look like
#product.select { |p| p.shipping_category == 'local' || shipping_category == 'zonal' }
Ideally you should use https://guides.rubyonrails.org/active_record_querying.html to filter data based on some condition at DB level use where method of Active Record.

Most elegant way to keep track of "last active" time

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.

"Archive" a table on rails

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

How to design a database in rails where all tables can be filtered by a global setting? Default global scope?

I am building a rails app and the data should be reset every "season" but still kept. In other words, the only data retrieved from any table should be for the current season but if you want to access previous seasons, you can.
We basically need to have multiple instances of the entire database, one for each season.
The clients idea was to export the database at the end of the season and save it, then start fresh. The problem with this is that we can't look at all of the data at once.
The only idea I have is to add a season_id column to every model. But in this scenario, every query would need to have where(season_id: CURRENT_SEASON). Should I just make this a default scope for every model?
Is there a good way to do this?
If you want all the data in a single database, then you'll have to filter it, so you're on the right track. This is totally fine, as data is filtered all the time anyway so it's not a big deal. Also, what you're describing sounds very similar to marking data as archived (where anything not in the current season is essentially archived), something that is very commonly done and usually accomplished (I believe) via setting a boolean flag on every record to true or false in order to hide it, or some equivalent method.
You'll probably want a scope or default_scope, where the main downside of a default_scope is that you must use .unscoped in all places where you want to access data outside of the current season, whereas not using a default scope means you must specify the scope on every call. Default scopes can also seem to get applied in funny places from time to time, and in my experience I prefer to always be explicit about the scopes I'm using (i.e. I therefore never use default_scope), but this is more of a personal preference.
In terms of how to design the database you can either add the boolean flag for every record that tells whether or not that data is in the current season, or as you noted you can include a season_id that will be checked against the current season ID and filter it that way. Either way, a scope of some sort would be a good way to do it.
If using a simple boolean, then either at the end of the current season or the start of the new season, you would have to go and mark any current season records as no longer current. This may require a rake task or something similar to make this convenient, but adds a small amount of maintenance.
If using a season_id plus a constant in the code to indicate which season is current (perhaps via a config file) it would be easier to mark things as the current season since no DB updates will be required from season to season.
[Disclaimer: I'm not familiar with Ruby so I'll just comment from the database perspective.]
The problem with this is that we can't look at all of the data at once.
If you need to keep the old versions accessible, then you should keep them in the same database.
Designing "versioned" (or "temporal" or "historized") data model is something of a black art - let me know how your model looks like now and I might have some suggestions how to "version" it. Things can get especially complicated when handling connections between versioned objects.
In the meantime, take a look at this post, for an example of one such model (unrelated to your domain, but hopefully providing some ideas).
Alternatively, you could try using a DBMS-specific mechanism such as Oracle's flashback query, but this is obviously not available to everybody and may not be suitable for keeping the permanent history...

Replace twitter-like usernames in a model's attribute automatically in Ruby on Rails

I'm having a very hard time trying to figure out how to do this the MVC way. I have a Comment model which holds a body attribute. This attribute may contain mentions such as the following:
Hi! This is me mentioning #someone.
Everytime someone posts a comment, an accessor method in the model converts all #mention to something like #user:231# where 231 would be the user's id. This way, if the mentioned user changes their username, I can still link & mention him without problems on older comments.
Now, I want to be able to access the body attribute and get the mentions already converted to links. It appears that doing this the MVC way, from within the model is not possible from what I have investigated.
Is there any easy way to do this? I don't wanna have to convert all the mentions on the controller because I think it could lead to repeated code and non-testable code.
Could anyone give me some advice on this?
Thanks!
Parsing the message into a particular format and then re-saving it in the database where it can then be edited at a later date is silly. I'm sorry to be so blunt, but doing it this way is fundamentally broken for one major reason: when a user goes to edit the message later on, they'll see the formatted text unless you format it back. Do you really want to be responsible for doing this?
I would hope not. Because you're a programmer, you're naturally lazy and would like to do things in as few steps as possible.
What I would recommend doing to solve this problem is to parse the message when you display it on the page. Before you go screaming at me that this is computationally intensive if you've got a large amount of hits, hear me out. When it's displayed on the page, you can then cache it like this:
<% cache comment do %>
# code goes here
<% end %>
This will store the final output in whatever cache you've set up with Rails, possibly Memcached or Redis, using a cache key which includes the comment's updated_at timestamp. Pay attention to this, it'll be useful later.
Retrieval from this cache will be faster than parsing it, and will be easier for you than to convert the message back and forth between its versions.
When a comment is updated, the updated_at timestamp will be different and so the new comment will be rendered first, then cached. In Memcached (so I'm told) it will clear the oldest cache key that hasn't been referenced if it needs more memory, thereby cleaning out the older comments.
Wouldn't you end up mangling the original message? Let's say I originally posted:
"Hi! This is me mentioning #bob."
From what I understand, you want to store this as:
"Hi! This is me mentioning #user:1#"
Now, if bob were to change his username to "fred", my message would now look like this:
"Hi! This is me mentioning #fred"
It may be easier to simply store a many-to-many relation between messages and users it mentions. That way, you still can easily see which messages mention a specific user, but you don't need to mangle the original message to do so.
If you need to convert each mention into a link, you could order the entries in the relationship table in the same order that they appear in the message.
maybe this gem help you https://github.com/twitter/twitter-text-rb
First, include Twitter::Autolink module from your class or helper
module ApplicationHelper
include Twitter::Autolink
end
From views, you can call it by :
<%= auto_link("Hi #john_doe, welcome to #ruby") %>
it will generate link to twitter john_doe username and ruby hashtag

Resources