views:

297

answers:

6

I'm adding some columns to one of my database tables, and then populating those columns:

def self.up
  add_column :contacts, :business_id, :integer
  add_column :contacts, :business_type, :string

  Contact.reset_column_information
  Contact.all.each do |contact|
    contact.update_attributes(:business_id => contact.client_id, :business_type => 'Client')
  end

  remove_column :contacts, :client_id
end

The line contact.update_attributes is causing the following Authlogic error:

You must activate the Authlogic::Session::Base.controller with a controller object before creating objects

I have no idea what is going on here - I'm not using a controller method to modify each row in the table. Nor am I creating new objects.

The error doesn't occur if the contacts table is empty.

I've had a google and it seems like this error can occur when you run your controller tests, and is fixed by adding before_filter :activate_authlogic to them, but this doesn't seem relevant in my case.

Any ideas? I'm stumped.

Here's my Contact model as requested:

class Contact < ActiveRecord::Base
  belongs_to :contactable, :polymorphic => true
  has_many :phone_numbers, :as => :callable
  has_many :email_addresses, :as => :emailable

  accepts_nested_attributes_for :phone_numbers
  accepts_nested_attributes_for :email_addresses

  validates_presence_of :first_name, :last_name
  validates_format_of :first_name, :last_name, :with => /^[-a-zA-Z ]+$/

  default_scope :order => 'first_name ASC, last_name ASC'

  def full_name
    "#{self.first_name} #{self.last_name}"
  end

  def to_s
    full_name
  end
end

Version info: Rails 2.3.5, Authlogic 2.1.3

rake db:migrate --trace output can be found online at pastie here: http://pastie.org/944446

Observer info:

I have an ActivityObserver that is observing my Contact model and creating an Activity using the after_update callback.

In my Activity model I am hackishly associating @current user with the activity being created using the before_save callback.

Here's the relevant snippets of code:

class ActivityObserver < ActiveRecord::Observer
  observe :contact

  def after_update(subject)
    Activity.create(:action => 'Updated', :subject => subject)
  end
end

class Activity < ActiveRecord::Base
  belongs_to :subject, :polymorphic => true
  belongs_to :user

  def before_save
    # FIXME: This is a messy hack way to get the user's id
    self.user = UserSession.find.record
  end
end

This is definitely where Authlogic is getting involved. KandadaBoggu is the winrar - thanks a lot for your insight!!!

In terms of fixes, I think that fundamentally there aren't any. If I want to create an Activity when my Contact is updated through a migration, by definition there is no @current_user to associate. I'll have a think about a way to get around this, but KandadaBoggu has definitely answered my question.

A: 

This should be as follows NO CHECK THOUGH.

belongs_to :contactable, :polymorphic => true, :foreign_key=>'client_id'
Salil
The polymorphic association works fine - adding an explicit `:foreign_key` actually breaks it. The issue is the Authlogic error that gets raised when I run the migration, not the association.
nfm
+1  A: 

I'm not sure if this will work, but worth a try:

replace this line:

contact.update_attributes(:business_id => contact.client_id, :business_type => 'Client')

with:

contact.update_attribute(:business_id, contact.client_id)
contact.update_attribute(:business_type, 'Client')

I cannot reproduce your problem, so I'm only guessing here:

For whatever reason update_attributes triggered this exception. I'm guessing it's in the validation phase. So we skip validation by using update_attribute.

If this still doesn't work, then the exception is triggered further down the stack. Worst case, you can write some SQL to update your models.

Aaron Qian
Worth a try, but unfortunately it didn't work.
nfm
A: 

This is plenty bizarre. I don't see how Authlogic is getting invoked in a migration at all.

Did you try rake db:migrate --trace?

One thought - you might try:

contact.business_id = contact.client_id
contact.business_type = 'Client'
contact.save_without_session_maintenance
zetetic
No dice. `save_without_session_maintenance` is an `Authlogic::ActsAsAuthentic::SessionMaintenance` method, and my `Contact` model doesn't `acts_as_authentic` so it doesn't have this method... `undefined method `save_without_session_maintenance' for #<Contact:0x9d94430>`.I will append relevant `rake db:migrate --trace` output to my question.
nfm
A: 

As what I know

 update_attributes(attributes) 

Accepts only one parameter. You probably mean

 update_attribute(name,value)
flopex
`update_attributes(attributes)` accepts one parameter (a Hash), and all arguments get globbed into that implicitly.
nfm
A: 

note: I am assuming you have a model called User in your app. If not, whichever one is using acts_as_authentic is what I mean.

Short version: Add the following to your migration:

User.controller = true

or

UserSession.controller = true

If that fixes it, then the issue is that your migration is trying to load your user model, which needs a controller set up to operate.

Looking through http://github.com/binarylogic/authlogic/blob/v2.1.3/lib/authlogic/session/activation.rb source is probably the place you want to start if what I've described doesn't work.

EDIT:

Add to the start of your migration:

module Authlogic::ActsAsAuthentic::Base::Config
  def acts_as_authentic(unsupported_options = nil, &block)
  end
end

This should disable the acts_as_authentic method so your model doesn't pull in all the extra stuff.

Daniel Heath
I do have a `User` model, and it `acts_as_authentic`.
nfm
Tried calling `controller=` on both `User` and `UserSession`. The former return an `undefined method` exception. The latter returns `undefined method `params' for true:TrueClass` - I believe that this method expects a controller object as its argument as per KandadaBoggu's reply.
nfm
I've updated the answer with something else I think would work.
Daniel Heath
+2  A: 

It looks like some how Authlogic session object is created when you update the Contact model. Do you have any observers OR before/after filters for Contact model?

From authlogic documentation for Authlogic::Session::Base.activated?

# Returns true if a controller has been set and can be used properly. 
# This MUST be set before anything can be done. Similar to how ActiveRecord 
# won't allow you to do anything without establishing a DB connection. In your 
# framework environment this is done for you, but if you are using Authlogic 
# outside of your framework, you need to assign a controller object to Authlogic 
# via Authlogic::Session::Base.controller = obj. See the controller= method for 
# more information.

One way to work around the issue by setting the controller before modifying the Contact model instance, i.e.:

def self.up
  add_column :contacts, :business_id, :integer
  add_column :contacts, :business_type, :string

  Authlogic::Session::Base.controller = 
                       Authlogic::ControllerAdapters::RailsAdapter.new(self)

  Contact.reset_column_information
  Contact.all.each do |contact|
    contact.update_attributes(:business_id => contact.client_id, 
              :business_type => 'Client')
  end

  remove_column :contacts, :client_id
end

Note: I haven't tested this code.

KandadaBoggu
YES! Good thinking!! See updated question for more info - comments suck at formatting.
nfm
Ok, so explicitly setting `Authlogic::Session::Base.controller` doesn't work, but I didn't expect it to either. I need to figure out a better way to determine the current user in my `Activity` model, or to skip that filter in my migration or something. Thanks again!
nfm
Add a `ignore_session` attribute to the model and set the value to `true` before running migration. Do not execute the filter if `ignore_session` is set to `true`.
KandadaBoggu