Writing an ActiveRecord plugin for Rails - ruby-on-rails

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
I guess monitor needs to be a class method of ActiveRecord::Base but how/where do I specify it in my plugin?
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"}?
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).
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?
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)

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.

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(*)

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.

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!

Related

Monkey patching a core class with business logic with Rails

I have a monkeypatched of ActiveRecord find with some business logic, for example:
# lib/core_extensions/active_record/finder_methods/finder.rb
module ActiveRecord
module FinderMethods
def find(*args)
return super if block_given?
#... business logic code => my_error_control = true
raise "My Error" if my_error_control
retorn = find_with_ids(*args)
end
end
end
retorn
I have not seen many examples like this, and this causes me a doubt:
Where should finder.rb be?
In this example, this file is in lib/core_extensions/... but if it contains business logic, I think finder.rb should lives in the folder app/core_extensions/ isn't it?
Edited, after Sergio Answer
things like this, are a bad practice?
# lib/core_extensions/nil_class/image_attributes.rb
# suport for product images attributes
class NilClass
def main_image(size,evita_video)
"/images/paperclip_missing/original/missing.png"
end
end
Where should finder.rb be?
Ultimately, it doesn't matter. It only matters that this code gets loaded. This mix of patching base libraries and adding business logic there looks like something that MUST be documented thoroughly (in the project's wiki or something like that). And if it is documented, then it doesn't matter. The code is where the documentation says it is.
That being out of the way, here's a design suggestion:
when user seeks a Family Family.find(params[family_id],session[:company_id]), this find will compare the company of the family result family.company witht the parameter
Why not do something like this:
family = current_company.families.find(params[:family_id])
where current_company can be defined as #current_company ||= Company.find(session[:company_id])
Here, if this company doesn't have this family, you'll get an exception.
Same effect*, only without any patching. Much more futureproof. You can even add a couple of rubocop rules to ensure that you never write a naked Family.find.
* it's not like you add that patch and rest of your code magically acquires super-powers. No. You still have to change all the finders, to pass that company id.
It's the first time I see such case :). I'd put it in app/core_extensions and check if live reloading works correctly with it. If not, I'd move it to lib/. (It's just a heuristic)
Edit:
Instead of extending NilClass I'd rather use regular NullObjects. It's really less surprising and easier to understand.
https://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object

Rails associated models with a method of the same name

I'm working with a massive legacy code base, so I am looking for advice concerning this particular issue, please, not suggestions of better high-level implementations.
A simplified version of what I'm working with:
class Order < ActiveRecord::Base
has_many :line_items
#other stuff
def balance
#some definition
end
end
class LineItem < ActiveRecord::Base
belongs_to :order
#other stuff
end
module Concerns
module LineItems
module Aggregates
extend ActiveSupport::Concern
#stuff
def balance
#some other definition
end
end
end
end
Order has a method called 'balance,' and a module of LineItem also has a method called 'balance.' It seems that most of the time (in most places in the code base), when specific_line_item.balance is called, it used the method definition under the LineItem module, but there are a couple of places where it instead calls the method from Order.
Is there any way in Ruby/Rails to specify on method call which of these two I'd like to use? OR is there probably something else going on here because Ruby doesn't have method overloading, so the problem I'm describing here isn't possible?
All relevant cases where either method is called are coming from a line_item (i.e. specific_line_item.balance), so I would think it would always choose the method closer to home, rather than making the associative jump and calling Order's 'balance' method without being told to.
EDIT:
Thanks for the responses! It seems I wasn't clear enough with my question. I understand the difference between
Order.first.balance
and
LineItem.first.balance
and that the balance method being called is the one defined within the class for that object. In the situation I'm describing, I observed, in the actual live app environment, that at a place in the code where
LineItem.find(some_id).balance
was called it output not the result that would be computed by the LineItem 'balance' method, but the one from the Order class.
So I had hoped to learn that there's some ruby quirk that might have an object call an associate's method of the same name under some conditions, rather than it's own. But I'm thinking that's not possible, so there's probably something else going on under the covers specific to this situation.
Firstly, ActiveRecord::Concern can change a lot of behaviour and you've left out a lot of code, most crucially, I don't know where it's being injected, but I can make an educated guess.
For a Concern's methods to be available a given object, it must be include'd in the object's class's body.
If you have access to an instance of the Order object, at any point you can call the balance method:
order = Orders.last # grab the last order in your database
order.balance # this will call Order#balance
And if you have the Order then you can also get the LineItem:
order.line_items.first.balance # should call the Concerns:: LineItems::Aggregates#balance
You can open up a Rails console (with rails console) and run the above code to see if it works as you expect. You'll need a working database to get meaningful orders and balances, and you might need to poke around to find a completed order, but Ruby is all about exploration and a REPL is the place to go.
I'd also grep (or ag or ack) the codebase looking for calls to balance maybe doing something like grep -r "(^|\s)\w+\.balance" *, what you want to look for is the word before .balance, that is the receiver of the "balance" message, if that receiver is an Order object then it will call Order#balance and if it is a LineItem object then it will call Concerns:: LineItems::Aggregates#balance instead.
I get the feeling you're not familiar with Ruby's paradigm, and if that's the case then an example might help.
Let's define two simple Ruby objects:
class Doorman
def greet
puts "Good day to you sir!"
end
end
class Bartender
def greet
puts "What are you drinking?"
end
end
Doorman and Bartender both have a greet method, and which is called depends on the object we call greet on.
# Here we instantiate one of each
a_doorman = Doorman.new
a_bartender = Bartender.new
a_doorman.greet # outputs "Good day to you sir!"
a_bartender.greet # outputs "What are you drinking?"
We're still using a method called greet but the receiver is what determines which is called.
Ruby is a "message passing language" and each "method" is not a function but it's a message that is passed to an object and handled by that object.
References
How to use concerns in Rails 4
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
http://guides.rubyonrails.org/command_line.html#rails-console

How to check if resque job has finished

I have a case scenario where I need to run multiple record updates in the background(using resque) and I want to give user visual indicator of how the task is running(eg started/running/finished).
One way of achieving this(which I can think of) is saving the current state into a table, then showing the state to user by simple page refresh.
Can anyone suggest a better solution of doing it?I want to avoid creating the whole migration, model, controller for this.
Thanks
As I've commented, resque-status gem could be useful for you. I am not sure if that is an answer but since you said that you do not want to create migration, model and controller for this. Thus, a gem might be the way to go.
From the job id you can get the status you are looking for, for example:
status = Resque::Plugins::Status::Hash.get(job_id)
status.working? #=> true
There is also a front-end called resque-web, check that out too.
You may use ruby's global variable $var_name = 'foo'. However I am not sure about it, because they are considered bad practice in rails, but in this case I see them reasonable, as soon as their name is very unique.
It can be done like (in case of resque):
class UpdateJob
#queue = data
def self.perform
$my_job_name_is_running = true
MyJobName.new.run
$my_job_name_is_running = nil
end
end
then you can access them from anywhere in the app:
while $my_job_name_is_running
puts "job is running..." if $my_job_name_is_running
sleep 3 # important to not overload your processor
end
Ruby global vars are not very popular. Check docs for more info https://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/globalvars.html

Rails move expensive method to task

I have these two methods in my model. One method looks up a single CatalogItem facebook like count, and another that loops through all active CatalogItems and finds their like counts using the aforementioned.
It takes a while to run through all active facebook likes...it might loop anywhere from 300-1000 objects; so i'd like to move this to some sort of cron, or whatever you guys suggest.
I was thinking I should add a column to CatalogItem called cached_fb_count, and adapt self.facebook_likes to write to that colimn whenever that task runs.
Is this the right approach? What would that task look like if it was running every 2 hours?
def self.facebook_likes
self.active.each_with_index do |i, index|
_likes = i.facebook_like_count
i.update_attribute(:cached_likes, _likes)
# puts "#{index+1} Likes: #{_likes} ########### ID: #{i.id} "
end
end
def facebook_like_count
item_like_count = JSON.parse(open("https://api.facebook.com/method/fql.query?query=select%20like_count%20from%20link_stat%20where%20url='https://www.foobar.com/catalog_items/#{self.id}'&format=json").read).first.flatten[1]
item_like_count = item_like_count + 1 if item_like_count > 0
end
Delayed_job is a perfect tool for doing asynchronous tasks. It runs in a separate process, relation database-based (Active Record) so it saves context of execution as a simple script invokation. And has a rich functionality inculding task's priority and scheldue. but If you tasks assumes huge queues, consider Resque gem. it uses Reddis as a storage for tasks and deals much faster with long queues.
Use whenever its very easy to set up. Here is the link : https://github.com/javan/whenever

Use find to initialize a constant?

Something like this:
class Category
SOME_CATEGORY = find_by_name("some category")
end
Category::SOME_CATEGORY
tried without a problem, but want to know if it is a bad idea, and the reasons if any..
thanks
If you don't want to hit the database each time you'll have to cache the model. There are several ways to do this, but one quick way is using Memoization. This was introduced in Rails 2.2.
class Category < ActiveRecord::Base
class << self
extend ActiveSupport::Memoizable
def named(name)
find_by_name(name)
end
memoize :named
end
end
Use it like this.
Category.named("some category") # hits the database
Category.named("some category") # doesn't hit the database
The cache should stay persistent across requests. You can reset the cache by passing true as the last parameter.
Category.named("some category", true) # force hitting the database
What do you want to do?
Maybe:
class Category
def self.some_category
Category.find_by_name("some category")
end
end
So you can call:
Category.some_category
=> <Category#2....>
It's not a terrible idea, but it's not really a good one either. It doesn't really fall in line with the way Rails does things. For one thing, you'll end up with a lot of ugly constant code. Too many ALL_CAPS_WORDS and your Ruby starts to look like C++. Bleah.
For another, it's inflexible. Are you going to make one of these constants for every category? If you add a new category two months from now, will you remember to update your Rails code, add a new constant, redeploy it and restart your server?
If it's important to you to be able to access categories very easily, and not repeat DB queries, here's a bit of metaprogramming that'll automatically look them up and create static methods like Lichtamberg's for you on first access:
def self.method_missing(category, *args) # The 'self' makes this a class method
#categories ||= {}
if (#categories[category] = find_by_name(category.to_s))
class_eval "def self.#{category.to_s}; #categories[#{category}]; end"
return #categories[category]
end
super
end
With this method in place, whenever you first call Category.ham, it'll create a class method that returns the value of find_by_name("ham") -- so that neither the query nor method_missing() runs again the next time you call it. This is pretty much the way the OpenStruct class works, BTW; look it up in the Pickaxe book if you want to learn more.
(Of course you'll still have the risk that, because these are all memoized, your Rails app won't reflect any changes you make to your category objects. This makes the assumption that changes won't happen or don't really matter. It's up to you to determine whether that assumption is valid for your app. You could always put an after_update callback in your code that resets ##categories if that's a problem; but at that point this starts to get complicated.)

Resources