Creating logs & history in Rails3 - ruby-on-rails

I'm trying to create some logs and history about users in my app.
For example, I'd like to know how many posts any user had total, on any given day. And some other cool metrics.
Off the bat, my assumption is that the only way to do this, is to have a cron job running some tasks, that calculates these numbers, and stores them for each user, every day, in a new table.
Is there a better or alternative way to go about this?

You could use a plug-in such as make_resourceful. In the case of posts, you could override the after :create method to post to a table that tracks the number of posts for the user. In the case of page views, you could override the after :show method.
You must include the the make_resourceful methd in the controller class. For example:
class BlogPostsController < ApplicationController
make_resourceful do
actions :all
response_for after :create do |format|
BlogPostAudit.create (:user => current_user)
end
end
end

Related

How to prevent actions at specific date and time?

I would like to allow the users to 'create' and 'update' their submissions (bets) until a specific date and time at which point their bets are final (can no longer be created or updated). This process would repeat each week.
I'm fairly new to Rails and I'm not sure if there is a term for this or what to search for.
Can anyone point me in the right direction?
Probably the easiest way to achieve this is just to add a before_filter (Rails 3.x) or before_action (Rails 4.x) to your controller. You can do so like this:
Assume you have submissions_controller.rb with create/update actions like so - add a before filter that will only apply to the create and update actions. You can then implement a private method in the controller to redirect the user back to your root_path or elsewhere and give a flash message as to why.
class PagesController < ApplicationController
before_filter :check_if_bets_are_final, :only => [:create, :update]
def create
...
end
def update
...
end
private
def check_if_bets_are_final
if Time.now >= Time.new(2014, 02, 20)
flash[:error] = "You can no longer modify or submit new bets!"
redirect_to root_path
end
end
end
Aside from your controller action though, it will probably be safer to implement a model-level validation/check to reject it if the date is past, just to be safe (or if you have other ways to update that object in the future). You can do this through the model hook before_save, in which you can pretty much do a similar check that I have given above.
Also, the other caveat is that comparing Time.now could be in a different timezone depending on where your server is. Just be cognisant of this when you do your checks, and cast the time properly with this in mind.
Since you didn't provide a specific implementation, I'm not quite sure if you're having trouble specifically with Ruby or Rails. However, given your question, I would store a datetime variable in your database when the user creates the bet. Every time the user tries to 'update' the bet, check in the database whether or not it's been past that specific time away from the bet creation. Hope this helps.

Destroy a post after 30 days from its creation

For learning purposes I created a blog, now I want to destroy a post automatically after 30 days from its creation. how can I do it?
This is my Posts controller
def index
#posts = Post.all
end
def create
#post = current_user.posts.new(post_params)
#post.save
redirect_to posts_path
end
def destroy
#post.destroy
redirect_to posts_path
end
I would set up a task with whenever that runs every 1 day.
To generate a task:
rails g task posts delete_30_days_old
Then on the created file (lib/tasks/posts.rb), add the following code:
namespace :posts do
desc "TODO"
task delete_30_days_old: :environment do
Post.where(['created_at < ?', 30.days.ago]).destroy_all
end
end
This is of course if you want to delete the posts that have more than 30 days, other answers might as well work but I would rather have my database with clean data that I'll use on my application.
Posts will be stored in your database. The model is what interacts with your database. Your controller never sees the database, it only sees what the model shows it. If you wanted to pull from the database using your model inside the controller you could do it with this code.
#posts = Post.where('created_at >= :thirty_days_ago', thiryty_days_ago: Time.now - 30.days)
Post in this code calls you app/model/Post.rb which inherited active record. .where is the active record method that looks at your database based on the stuff you define. Here we have defined to pull only rows where the created_at column has a time in it that is 30 days ago.
If you look inside your database you'll notice the created_at column was automagically put in there for you.
Along with the aforementioned whenever gem, you can also use two gems called Sidekiq and Sidetiq for scheduling tasks/workers.
I've been using these on a large app at work and am very pleased with it. It's fast (uses Redis, added with a simple gem, reliable, and easy to use).
# in app/workers/clean_posts.rb
class CleanPosts
include Sidekiq::Worker
include Sidetiq::Schedulable
recurrence { monthly }
def perform
# stealing from toolz
Post.where('created_at >= :thirty_days_ago', thiryty_days_ago: Time.now - 30.days).destroy_all
end
end
This will, however, remove the posts from your DB and they will no longer be accessible by your application.
To achieve desired result, you need to change your index action like this:
def index
#posts = Post.where(created_at: 30.days.ago..Time.now)
end
In such way, you won't need to destroy posts and you will get the desired result.
If you need to limit access to the older posts, then you can use:
def show
#post = Post.where(created_at: 30.days.ago..Time.now).find(params[:id])
end
And, if we are speaking about code beauty, then you should move where(created_at: 30.days.ago..Time.now) part to a scope in your model, like this:
class Post
...
scope :recent, -> { where(created_at: 30.days.ago..Time.now) }
end
And use it like this:
Post.recent #=> to get list of recent posts
Post.recent.find(params[:id]) #=> to get recent post with specified id
You can not do that from your controller, you need to add some functionality to your application.
You will need a cron job running everyday that will look for posts that are more than 30 days old and destroy them.
eg Post.where('created_at < ?', 30.days.ago)
For handling the cron jobs better you might consider using the whenever gem that helps a lot and keeps the cron setup in your app.

Testing a Rails ApplicationController method with rspec

Just having trouble figuring out how to test an action that utilizes a private ApplicationController method. To explain:
A User has and belongs to many Organisations (through Roles), and vice versa. An Organisation has a bunch of related entities, but in the app a user is only dealing with a single Organisation at a time, so for all of my controller methods I want to scope with a current organisation. So in my ApplicationController:
private
# Returns the organisation that is being operated on in this session.
def current_org
# Get the org id from the session. If it doesn't exist, or that org is no longer operable by the current user,
# find an appropriate one and return that.
if (!session[:current_organisation_id] || !current_user.organisations.where(['organisations.id = ?', session[:current_organisation_id]]).first)
# If an org doesn't exist for this user, all hope is lost, just go to the home page
redirect_to '/' if (!current_user.organisations.first)
# Otherwise set the session with the new org
session[:current_organisation_id] = current_user.organisations.first.id;
end
# Return the current org!
current_user.organisations.first
end
First things first, I don't know if this is the best way to scope. I'd love some elegant default_scope thing, but with the use of session variables this seems problematic. Anyway, the above let's me do this in controllers:
class HousesController < ApplicationController
def index
#houses = current_org.houses
end
end
So now I want to use rspec to test my controllers. How can I make sure the current_org method can be called and returns one of the factory_girl Organisation models so that I can write the spec. I'm just confused at how best to bring the factory, spec, action and AppControlled method together.

Create Rails model with argument of associated model?

I have two models, User and PushupReminder, and a method create_a_reminder in my PushupReminder controller (is that the best place to put it?) that I want to have create a new instance of a PushupReminder for a given user when I pass it a user ID. I have the association via the user_id column working correctly in my PushupReminder table and I've tested that I can both create reminders & send the reminder email correctly via the Rails console.
Here is a snippet of the model code:
class User < ActiveRecord::Base
has_many :pushup_reminders
end
class PushupReminder < ActiveRecord::Base
belongs_to :user
end
And the create_a_reminder method:
def create_a_reminder(user)
#user = User.find(user)
#reminder = PushupReminder.create(:user_id => #user.id, :completed => false, :num_pushups => #user.pushups_per_reminder, :when_sent => Time.now)
PushupReminderMailer.reminder_email(#user).deliver
end
I'm at a loss for how to run that create_a_reminder method in my code for a given user (eventually will be in a cron job for all my users). If someone could help me get my thinking on the right track, I'd really appreciate it.
Thanks!
Edit: I've posted a sample Rails app here demonstrating the stuff I'm talking about in my answer. I've also posted a new commit, complete with comments that demonstrates how to handle pushup reminders when they're also available in a non-nested fashion.
Paul's on the right track, for sure. You'll want this create functionality in two places, the second being important if you want to run this as a cron job.
In your PushupRemindersController, as a nested resource for a User; for the sake of creating pushup reminders via the web.
In a rake task, which will be run as a cron job.
Most of the code you need is already provided for you by Rails, and most of it you've already got set in your ActiveRecord associations. For #1, in routes.rb, setup nested routes...
# Creates routes like...
# /users/<user_id>/pushup_reminders
# /users/<user_id>/pushup_reminders/new
# /users/<user_id>/pushup_reminders/<id>
resources :users do
resources :pushup_reminders
end
And your PushupRemindersController should look something like...
class PushupRemindersController < ApplicationController
before_filter :get_user
# Most of this you'll already have.
def index
#pushup_reminders = #user.pushup_reminders
respond_with #pushup_reminders
end
# This is the important one.
def create
attrs = {
:completed => false,
:num_pushups => #user.pushups_per_reminder,
:when_sent => Time.now
}
#pushup_reminder = #user.pushup_reminders.create(attrs)
respond_with #pushup_reminder
end
# This will handle getting the user from the params, thanks to the `before_filter`.
def get_user
#user = User.find(params[:user_id])
end
end
Of course, you'll have a new action that will present a web form to a user, etc. etc.
For the second use case, the cron task, set it up as a Rake task in your lib/tasks directory of your project. This gives you free reign to setup an action that gets hit whenever you need, via a cron task. You'll have full access to all your Rails models and so forth, just like a controller action. The real trick is this: if you've got crazy custom logic for setting up reminders, move it to an action in the PushupReminder model. That way you can fire off a creation method from a rake task, and one from the controller, and you don't have to repeat writing any of your creation logic. Remember, don't repeat yourself (DRY)!
One gem I've found quite useful in setting up cron tasks is the whenever gem. Write your site-specific cron jobs in Ruby, and get the exact output of what you'd need to paste into a cron tab (and if you're deploying via Capistrano, total hands-off management of cron jobs)!
Try setting your attr_accessible to :user instead of :user_id.
attr_accessible :user
An even better way to do this however would be to do
#user.pushup_reminders.create
That way the user_id is automatically assigned.
Use nested routes like this:
:resources :users do
:resources :pushup_reminders
end
This will give you params[:user_id] & params[:id] so you can find your objects in the db.
If you know your user via sessions, you won't need to nest your routes and can use that to save things instead.
Using restful routes, I would recommend using the create action in the pushup_reminders controller. This would be the most conventional and Restful way to do this kind of object creation.
def create
#user = User.find(params[:user_id]
#reminder = #user.pushup_reminders.create()
end
If you need to check whether object creation was successful, try using .new and .save

Is it better practice to have one URL for each bulk update or many URLs?

I have a user maintenance page. This page has a list of users where the admin can do bulk updates on the users he selects. Bulk updates include: activate, deactivate, and update roles to.
Should there be one URL I POST to, such as /users/bulk_update.json, where I then pass in the list of IDs and the method type. And in my bulk_update action, I update the IDs according to the method.
Or should there be a URL for each method, such /users/bulk_update_activate, /users/bulk_update_deactivate, and /users/bulk_update_roles?
The quick answers is: it depends! :)
If your updates type share many of the code logic.
1) Use filter:
class FirstController < ApplicationController
# Other controller code
before_filter :prepare_update
after_filter :finalize_update
def bulk_update_activate
# Do something here
end
def bulk_update_deactivate
# Do something here
end
end
2) Use a single action:
class SecondController < ApplicationController
# Other controller code
def bulk_update
case params[:operation]
when :activate then
# Do something here
when :deactivate then
# Do something here
end
end
end
If your updates are completely indipendent, then you should write different actions.
In my projects I usually find myself in using the first approach.
Hope it will be useful.

Resources