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.

Serving a file from your Rails app from another server protected by basic authentication

Interesting problem: Retrieve a document from one web service protected by basic auth (or some authentication mechanism) and serve it via a public link on another website.

It ended up boiling down to this code in the controller (reduced to be in the same method instead of factored out as it was.)

  def get_download
    response = Net::HTTP.start('localhost', '3010', {} ) do |http|
      request = Net::HTTP::Get.new('http://localhost:3010/my_document.pdf')
      request.basic_auth 'username', 's3cretpassword'
      http.request(request) do |response|
        send_data(response.body, filename: 'set_filename.pdf')
      end
    end
  end

Moving Static Files to the Assets Pipeline (upgrading from 3.0 to 3.1+)

For those who have done the upgrade, this is probably second nature: Recently stumbled upon static files in the public/javascripts directory and wanted to get them moved to the $RAILS_ROOT/app/assets/ directory.

This RailsCast, 282 Upgrading to Rails 3.1 provides most of the notes you need on doing this.

The first key is to include:

config.assets.enabled = true

Now, all those public folders:

public/javascripts
public/stylesheets
public/images

...need to be moved to:

app/assets/javascripts
app/assets/stylesheets
app/assets/images

Also... whenever the assets get served, the directory name under assets is removed, so that app/assets/myspecialfiles/stuff.png becomes http://example.com/assets/stuff.png

The latest info on the asset pipeline at

Nesting resource routes with alternate scopes and keys + strong parameters.

DISCLAIMER:
This is a contrived example translated from a case where it actually made way more sense to break the familiar nested resource pattern. Also, I haven't double-checked how Rails' pluralization rules would affect the naming of any given Species.

Say you have a pair of models:

class Species
  # id
  # latin_name_with_underscores
end
 
class Animal
  # name
  # species_id
end

Normally, you'd nest resource routes in your routes.rb file something like:

resources :species do
  resources :animal
end

And your routes would look something like:

/species/:species_id/animals
/species/:species_id/animals/:animal_id

But... what if you wanted different nesting, resource ids, and route names?

namespace :latin_species do
  scope '/:latin_name' do
    resources :animals
  end
end

Now you have:

/latin_species/:latin_name/animals
/latin_species/:latin_name/animals/:id

In your controller app/latin_species/animals_controller.rb, you can now do the following to set the appropriate parent and child:

class LatinSpecies::AnimalsController < ApplicationController
  before_action :set_species_and_animal
#
#
#
  def set_species_and_animal
    @species = Species.find_by(latin_name_with_underscores: params[:latin_name])
    @animal = Animal.find(params[:id]) if pararms[:id]
  end
end

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

foreigner foreign_key on a different column in source and target table.

In the foreigner gem, you can specify the foreign_key column on the source table by using the column option.

class Cats < ActiveRecord::Migration
  def change
    create_table :cats do |t|
      t.integer :color_id
      t.integer :second_color_id

      t.timestamps
    end
    add_foreign_key :cats, :colors, column: :second_color_id
  end
end

But what about the target table? This can be accomplished using the primary_key option.

class Dogs < ActiveRecord::Migration
  def change
    create_table :dogs do |t|
      t.integer :color_id
      t.integer :breed_name

      t.timestamps
    end
    add_foreign_key :dogs, :breed, column: :breed_name, primary_key: :name
  end
end

Of course, the model has to change as well:

class Dog < ActiveRecord::Base
  belongs_to :breed, primary_key: :name
end

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

Passing Additional Arguments to FactoryGirl Factories

I wanted to create a factory that would link various states to my model, but from a specified list instead of automatically generated. I discovered that these are transient attributes:

FactoryGirl.define do
  factory :business do
    name "MyString"
#
#
#
    trait :with_states do
      ignore do
        state_list []
      end
      # add association equal to state code
      after(:create) do |business, evaluator|
        evaluator.state_list.each do |state_code|
          business.states << State.find_by(state_code: state_code)
        end
      end
    end
  end
end

This factory can now be called with:

  FactoryGirl.create(:business, :with_states. state_list: %w(KY IN))

Of course, now that I'm passing in the attributes, I can probably just use:

FactoryGirl.define do
  factory :business do
    name "MyString"
#
#
#
    ignore do
      with_states []
    end
    # add association equal to state code
    after(:create) do |business, evaluator|
      evaluator.with_states.each do |state_code|
        business.states << State.find_by(state_code: state_code)
      end
    end
  end
end

and then the call is even simpler:

  FactoryGirl.create(:business, with_states: %w(KY IN))

Rails 4, Empty Hashes, Strong Parameters, exception_notification and filtering out my new ActionController::ParameterMissing errors

I'm finally coming around to the "strong parameters" pattern encouraged (required?) in Rails 4.

One thing that I did notice was that missing the base parameter raised an ActionController::ParameterMissing error. I discovered this via controllers tests generated by rspec-rails when I created a controller scaffold, which generates tests for "Invalid Params":

In the controller spec:

describe "with invalid params" do
  it "re-renders the &#039;new&#039; template" do
    # Trigger the behavior that occurs when invalid params are submitted
    User.any_instance.stub(:save).and_return(false)
    post :create, {:user => {  }}, valid_session
    response.should render_template("new")
  end 
end

In the controller:

  def user_params
    params.require(:licensee).permit(:parameter1, :parameter2)
  end

Despite the fact that params[:licensee] exists, the empty hash triggers the ActionController::ParameterMissing error. That's fine. I can expect { post :create, {:user => { }}, valid_session }.to raise_error(ActionController::ParameterMissing), or eliminate the test altogether.

However, with exception_notifier, I don't really want to be notified about these errors, so I configured my environment files to filter this exception (and also CanCan::AccessDenied).

  config.middleware.use ExceptionNotification::Rack,
    :ignore_exceptions => %w(CanCan::AccessDenied ActionController::ParameterMissing) + ExceptionNotifier.ignored_exceptions,
    :email => {
      :email_prefix => "[#{Rails.env}] project name exception: ",
      :sender_address => %{"Exception Notifier" <exceptions@example.com>},
      :exception_recipients => [ `git config user.email`.chomp ]
   }