views:

389

answers:

4

I'm writing my first rails plugin and could use a little help. In a very simplified way, I'd like to do allow the developer to specify a value which I can count through a rake task. I'm thinking of something like this...

class User < ActiveRecord::Base
    monitor "Users", count
    monitor "Active Users", count("activated_at != NULL")
end
  1. I guess monitor needs to be a class method of ActiveRecord::Base but how/where do I specify it in my plugin?
  2. The argument to the monitor function shouldn't be the value but a block of code to execute. I'm not quite sure of the best way to specify this and keep the syntax simple. Perhaps it'll have to be monitor "Active Users", {count "activated_at != NULL"}?
  3. I'd prefer if the developer didn't have to specify User.count, just count, i.e. it would pick up the Class automatically (and the blocks will be called on the class not the instance). If this isn't possible, I guess there's no reason to put the monitor statements into the model (see #5).
  4. The actual counting of these values (i.e., execution of the blocks) will be done by a rake task offline. What should the monitor function do to make these blocks available to the rake task? Store them in a class variable?
  5. Perhaps the monitor statements don't need to be specified in the model at all. Maybe it clutters it up so I'd welcome any alternative places to put them.

I'm just sketching out my ideas at the moment and trying to figure out what is/isn't possible in Ruby. Any help appreciated.

Update: I'll try to be clearer on the plugin's purpose. I want the developer to be able to define metrics which should be monitored by the rake task. The rake task will iterate over those metrics and write the values to a file (I've simplified this a bit). The rake task will be very simple, something like rake monitors:update (i.e., no params required)

A: 

Try looking at the code for named_scope

whats the design for the rake task looking like?

rake monitor:user:active_users ?

OT:

activated_at is not null is the SQL that you want

Come to think of it, why not forget defining monitor, and just use named_scopes ? where instead of returning a select *, you do a select count(*)

Omar Qureshi
Thanks, you're right about the SQL - it was just an example. I reckon named_scopes will be very useful but it doesn't quite solve my problem. I've updated the question with more information on what the plugin is trying to achieve.
hopeless
+1  A: 

You are probably putting the definition of the rake tasks in the wrong place. The model should only contain logic that is valid for any of its consumers, and not concern itself with specific applications like rake.

A better approach may be to define some named scopes in your models, and specify the actions you wish to be available in your rake tasks. The named scopes can be reused easily in other areas of your application. A model may look like this (note that this is a Rails feature -- no work required on your part):

class User < ActiveRecord::Base
  named_scope :active_users, :conditions => "activated_at != NULL"
end

And then you would create a very simple DSL that can be used within rake files (e.g. in lib/tasks/count.rake). Something that will allow you to do this, for example:

require "your-plugin"
namespace :count do
  # Make your plugin rewrite this internally to User.count
  YourPlugin::CountTask.new :users

  # Make your plugin rewrite this to User.active_users.count
  YourPlugin::CountTask.new :users, :active_users

  # Perhaps allow usage of blocks as well?
  YourPlugin::CountTask.new :users, :complicated do
    User.count(complex_conditions)
  end
end

This should then provide the user with tasks named count:users, count:users:active_users and count:users:complicated.

molf
I take the point yourself and Omar have made about named_scopes. Definitely useful. I've updated the question with more information. I hope I make more sense this time.
hopeless
A: 

Something like this should do what you want:

module Monitored
  @@monitors = []
  def self.monitor(name, method)
    @@monitors.push [name, method]
  end
  def self.run_monitor(name)
    send @@monitors.select{|m| m[0] == name}[0][1]
  end
end

Untested, but you get the idea, I hope.

Michael Sofaer
A: 

Thanks for all your help, however I went with a different approach (extracted below).

Instead of specifying the attributes in the models, I used an approach seen in the whenever gem. I placed a ruby file "dashboard.rb" in my config directory:

dashboard "Users", User.count
dashboard "Activated Users", User.count('activated_at')

My lib consists of two functions:

  def self.dashboard(name, attribute)
      puts "** dailydashboard: #{name} = #{attribute.to_s}"
  end

  def self.update(file)
    eval File.read(file)
  end

Basically, my rake task calls update, which loads dashboard.rb and evaluates it and repeatedly calls the dashboard function, which outputs this:

** dailydashboard: Users = 2
** dailydashboard: Activated Users = 1

Sorry for going around the houses a little bit. For background/offline things this seems like a very simple approach and does what I need. Thanks for your help though!

hopeless