Rails 4, phantom ArgumentError: wrong number of arguments (0 for 1) on UserSession.find for AuthLogic

This was a total pain to locate, as the exceptions being raised were pointed to UserSession.find in Authlogic::Session::Persistence.

gems in question:

  activerecord-session_store 0.1.1
  authlogic 3.4.6
  activerecord 4.2.3
  rails 4.2.3

I noticed that the only meaningful difference between the two environments was the following instance variable in Rails.application.config:

irb> pp Rails.application.config
.
.
.
 @logger=
  #,
   @level=1>
.
.
.

Commenting out this initialization in the environment allowed the application to work again.

   config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new('name of log'))

Searching on "usersession syslog rails 4" results in the following issue as the third search result: Version 0.1.1 breaks Syslog::Logger setups There is a fix in master, but it doesn't seem to have been published. I had promoted activerecord-session_store to 0.1.1 because DEPRECATION WARNING: `#quietly` is deprecated in rails-4.2.0.beta4, but it looks like locking the gem at 0.1 will work otherwise except for noisy tests.

has_many :through, scoped to a value on the other side of the :through relationship

Here goes another round of wrapping my head around what rails associations are looking for.

This time, I'm trying to scope a collection on the other side of a has_many :through to an attribute on that model.

Contrived exhibit A:

class Gym < ActiveRecord::Base
  # gym has a boolean "open"
end

class TrainerGym < ActiveRecord::Base
  has_one :trainer
  has_one :gym
end

class Trainer < ActiveRecord::Base 
  has_many :trainer_gyms
  has_many :gyms, through: :trainer_gyms
end

I want to create an association of `open_gyms` that my `Trainer` is connected to.

The end result looks like this for the `Trainer` model:

class Trainer < ActiveRecord::Base 
  has_many :trainer_gyms
  has_many :gyms, through: :trainer_gyms

  has_many :open_trainer_gyms ->{ includes(:gym).where(gyms: {open: true}) }, class_name: TrainerGym
  has_many :open_gyms, through: :open_trainer_gyms, source: :gym
end

The scope:

->{ includes(:gym).where(gyms:{open: true}) }

`includes` the association `gym` from `TrainerGym`. The `where` clause specifies the table `gyms` and the nested hash is the condition on `gyms` to match (`{open: true}`).

The `class_name: TrainerGym` tells the association `open_trainer_gyms` that it is a collection of the model `TrainerGym`, since that cannot be determined by reflection/inflection magic.

The `open_gyms` association piggy-backs the `open_trainer_gyms` association, but the `:through` relationship needs to be told that the `gym` association on `TrainerGym` is how it gets to the other side, since `open_gyms` can't be converted to `gym` automatically by the reflection and inflection magic.

Conditions on a has_many :through and ActiveRecord 4.1 enum

Say you have a set of models (model names sanitized for generic posting) where the join table in the middle of a has_many :through has an enum that you want to add a filtered association for.

class Student < ActiveRecord::Base
    has_many :student_grades
    has_many :grades, through: :student_grades
  end

  class StudentGrade < ActiveRecord::Base
    enum approval_status: [:not_reviewed, :rejected, :approved]
    belongs_to :student
    belongs_to :grade
  end

  class Grade
  end

I ended up creating two additional has_many associations on Student to accomplish this. I have to specify a condition on :approved_student_grades, and tell it what class name (StudentGrade) to point to because reflection through the association name won't work. I really dislike the verbosity of StudentGrade.approval_statuses[:approved] as a way to get the integer value of the enum (in this case, 2). For the :approved_grades association, I also have to repeat a similar process, except I have to specify both the class_name: and the source: due to reflection not working for :approved_grades.

class Student < ActiveRecord::Base
  has_many :student_grades
  has_many :grades, through: :student_grades
  has_many :approved_student_grades, -> {where approval_status: StudentGrade.approval_statuses[:approved] }, class_name: "StudentGrade"
  has_many :approved_grades, through: :approved_student_grades, class_name: "Grade", source: :grade
end

I'd be surprised if there wasn't a cleaner way to do this, but enum is new in Rails 4.1, so that may be part of my problem.

has_many :through, self referential models

Hat tip to [has_many :through self-referential example] that sent me down the correct path on this. The challenge I had went a little deeper than the aforementioned post, however. I'm going to reuse the friend example, but with a twist.

class Person < ActiveRecord::Base
  # id :integer
end
class Friendship < ActiveRecord::Base
  # source_friend_id :integer
  # target_friend_id :integer
end

In the above contrived example, Person is a friend who initiated the friendship (source_friend), and a friend that was sought for friendship (target_friend). For the friendship, reflection won't work on either connection, so we need to specify the class_name:.

class Friendship < ActiveRecord::Base
  belongs_to :source_friend, class_name: "Person"
  belongs_to :target_friend, class_name: "Person"
end

Including the friendship would normally be easy. That's has_many :friendships. However, there's no explicit mapping back to Person for the Friendship. ActiveRecord will attempt to use friendships.person_id to no avail, so foreign_key must be specified to tell ActiveRecord how to map back the has_many relationship.

  class Person < ActiveRecord::Base
    has_many :friendships, foreign_key: :source_friend_id
  end

Now, the friends you've "made" can be mapped by putting the target_friend as the source for the has_many :through.

  class Person < ActiveRecord::Base
    has_many :friendships, foreign_key: :source_friend_id
    has_many :made_friends, through: :friendships, source: :target_friend
  end

Principle of Least Surprise (Astonishment), foreign keys, and Rails

In yesterday's post, I sorted through the foreigner gem to figure out how to change the reference column (primary key) that a foreign_key maps to.

The problem here is that, unless your Rails project(s) has grown up referencing "natural" primary keys instead of the autoincremented id implicit in an ActiveRecord::Migration, avoiding creating surprise by not referring to an artificial primary key actually adds surprise: The instinct of someone reading your code will be to assume that the foreign key maps to the auto-id of the foreign model.

includes and has_many :through associations

I've been trying to mentally absorb how includes and associations work when trying to query a has_many :through association.

My understanding at this point:

The symbol in the includes() is the same as the association name on whatever model you have. For example:

class Account < ActiveRecord::Base
  has_many :account_destinations
  has_many :destinations, through: :account_destinations
end

class AccountDestination < ActiveRecord::Base
  has_one :destination
  # has an active_destination attribute
end

class Destination < ActiveRecord::Base
  # has several attributes that we might want to use to filter account_destinations on
end

The association used in includes for account_destinations.includes is :destination, because AccountDestination has_one :destination. Meanwhile, in the where clause, the hash key for specifying what values to match in Destination refers to the table name, or destinations.

class Account
.
.
  def some_method
    account_destinations.includes(:destination)
      .where
      .not(destinations: { name: %w(some list of names) } )
  end
end