DRY (don't repeat yourself) way / Hook to Activerecord object (to store IP address on every object created)

As part of my project, I need to store IP address for every object that was created.

  User   Topic   Payment   and more...

Now all these records have the following attribute in their table called "ip_address_on_create".

One way to do this is to put this code everywhere in the controllers:

   user.ip_address_on_create = request.remote_ip ,.... and so forth.

Now to conform to DRY (don't repeat yourself), I see the mechanism: after_create (model) and after_filter ( controller). I am not really sure how to tie this thing together. Would appreciate any tips or Ruby-Fu .

Thanks in advance! Chris

I haven't tested this at all, and to be quit honest it feels a bit hackish, but the following comes to mind:

before_filter lambda { params.merge! :ip_address_on_create => request.remote_ip }

Add the appropriate attribute to attr_accessible and you'll be good to go.

or rather you're more likely going to be doing params[:user].merge!... but you get the idea

Might the DRYest way to do this be to use a polymorphic association for the IP address? Colin

Have a look at active_record::observers and wrap the save method with an update to the ip address. Declare a class level variable in application.rb with a before_filter that populates the IP into the variable (and subsequently used by the observer).

Makund - it is quite likely that your solution will break when
multithreading arrives (as will will loads of other stuff)

009/6/10 Jodi Showers <jodi@nnovation.ca>:

Makund - it is quite likely that your solution will break when multithreading arrives (as will will loads of other stuff)

Could you explain why please, for the uninitiated? Thanks Colin

Basically... the class variable is shared by all instances of the controller in question spawned by the Rack server process. Multithreaded Rails handles multiple requests simultaneously, and thus multiple simultaneous instances of the controller. Two requests come along, one from 1.1.1.1, then one from 2.2.2.2 immediately after. before_filter from 1.1.1.1's controller instance sets class variable accordingly, then before_filter from 2.2.2.2's instance overwrites the variable. Controller instance from 1.1.1.1 now uses 2.2.2.2's IP, and shinanagins ensue.

> > Makund - it is quite likely that your solution will break when > > multithreading arrives (as will will loads of other stuff)

You could try using Thread.current in your model when storing your class variable.

For example, in your User model, you could have the following methods:

def self.ip_address=(ip_address)     Thread.current[:ip_address] = ip_address end

def self.ip_address     Thread.current[:ip_address] end

You would set these as part of your authentication process

User.ip_address = request.remote_ip

Then you might be able to use this in a :before_save filter in your models

before_save :set_ip_address

def set_ip_address   self.ip_address = User.ip_address end

Replace the class variable with a method call instead?

God please tell me he did not suggest to use threads in order to add an argument. -- Posted viahttp://www.ruby-forum.com/.

Here is a brief discussion on Thread.current used in class variables.

http://coderrr.wordpress.com/2008/04/10/lets-stop-polluting-the-threadcurrent-hash/

As there is clearly debate about the robustness of using this method, I offer it only as a suggestion. It has worked for me.