Implementing a delayed association in Rails - ruby-on-rails

Here's a programming challenge I'm facing. I'm new at this...
I'd like to do this the Rails Way, with elegant code, and learn something in the process.
In my app, Players track an Action (25 minutes of focus starting on the half-hour). The action#new form loads at the beginning, but is not saved until the end of the 25 minutes. The last 2 minutes are a retrospective and include some required "how did it go" questions.
I now want to add an Insights table. I'm picturing a separate, small "new Insight" form that blends into the larger "new Action" form. I want to save the Insight at whatever time it comes to me.
I also want the Insight to be associated with that particular Action... even though the Action hasn't been created yet, and I don't know what id it will have (somebody else may record their Action before I do). If an Insight was created but the Action fails, I'm not yet sure what to do... I could allow Action-less Insights.
How do I implement this? A temp variable? Where would that go?
Again, I'm brand new at this, and I appreciate your support.
Mihai
TheActionGame.com

I'm note sure about what your action represent, but even if you have some time-based principle, nothing prevent your for saving the Action before (for a browser-based application, 25 minutes is a lot of time during which a lot of bad things can happen, like a refresh or a connection failure).
My advice would be to create (save) the Action immediately, thus allowing your Insight to be linked to it. I would add a "status" column to Action, with something like "new" (the base status), "confirmed" and other if needed.
You can then work only on the "confirmed" Actions, and eventually delete the "new" one on a regular basis (like all that are older than 30 minutes, with your thresold of 25).
Hope this make sense (more design than Rails, anyway).

You could add a column to your actions "insight_ids" as an array and implement it as a has_and_belongs_to_many.
Then when you have an insight, record the insight's id in the array

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.

How do I update a param in Rails before it submits, based on a selection

Sorry if this is a super nooby question, but I have been trying to figure out this solution for a while.
Basically, I have a field in a form which requests a length of time. Currently it accepts numbers as months in a float, so if you want to put in 15 days, you would have to enter "0.5". I added a radio field which allows the user to select a type (months or days).
I tried adding
if params[:length_type] == "days"
params[:length] = params[:length]/30
end
to the controller but I realized that even though the parameter was changing (as seen in debugger), it was being submitted first, which meant that if the user chose 15 days it would still be submitted as 15 months.
Next I tried adding this code to the model in a before_create where I was informed that:
undefined local variable or method `params' for ...
I looked it up and apparently params are not accessible in the model, and more so if they were it would break MCV practice.
I am pretty confused as to where to go from here, so any help would be appreciated.
Thank you for taking the time to read my question.
(BTW I don't want to do this in JS because I am worried about the possibility of it being off on a users computer, unless there is a way around that?)
You need to perform the action (change params) in the update method in the controller before the thing.update method is called in it. You do that if the record exists. If it is a new record you will need to do the same thing in the controllers create method before thing.save is called.

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.

How to break logic and avoid coupling in this scenerio

I have a item review system where people vote on an item.
The UI also displays how they voted, up/down.
The user can get points when they vote.
The item object that they vote on, has to reflect the # of voters and other stats.
So there is allot of things going on here, like if someone voted previously, I have to change their vote and therefore update their statistics and the statistics on the object voted on.
How can I possible break these different tasks out, since they all seem to be highly coupled with each other and I cant' figure out a way how to avoid having my code have all these different interactions mixed together.
Example method:
Set_Vote
1. is this an old vote?
-> if old vote, change old vote to new vote value
-> otherwise, insert new vote
2. update user stats
3. update item voted on stats
4. return a json response to the UI with information to update the screen via ajax etc.
Now it may seem easy to de-couple until you get to coding. Like for example, I can't really set the user stats unless I know the outcome of the logic from #1 since if its an old vote, I have to reverse what happened earlier, and then apply the new values (same with #3).
Should the method in #1 have a very rich return object that tells me things like 1) it was an old vote, or it wasn't? what was the outcome of the attempt to vote? (maybe the vote was rejected for whatever reason in my logic).

Rails - Rate Limiting a Model's Create

I'm looking to see if there is a smart way to do something like the following...
In my app I have projects. I want to prevent a user from adding more than 10 projects. My understand after only having used rails for a few weeks is, that I should make a helper in my model for this, does that sound right?
Also should I do this at the model/helper level or is this something that should be done for all models with some type of setting file?
So the idea is, when the user goes to create a new project, before_create, it checks, if the user has 10+ projects already, is says, sorry not at this time? ALso, interested in how to output the error msg, but 1 step at a time for a newbie.
thanks
Doing this as a validation method is pretty straightforward. In Rails 3 you just declare a method to be run during validation and it has an opportunity to add errors if the situation arises:
class Project
validate :user_can_create_projects
protected
def user_can_create_projects
if (user and user.projects.count >= 10)
errors.add_to_base("You have created too many projects.")
end
end
end
This is not an entirely bullet-proof method as there is a very small chance that someone might be able to create a project between the interval when you check the count and when you actually create the project. That sort of thing has a much greater chance of happening when someone double-clicks a form submit button, for instance, but in practice is relatively rare.

Resources