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


#1

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


#2

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.


#3

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


#4

2009/6/8 Chris removed_email_address@domain.invalid:

called “ip_address_on_create”.
or Ruby-Fu .

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


#5

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).


#6

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


#7

009/6/10 Jodi S. removed_email_address@domain.invalid:

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


#8

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


#9

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.


#10

Replace the class variable with a method call instead?


#11

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

God please tell me he did not suggest to use threads in order to add an
argument.


#12

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.