Updates from April, 2014 Toggle Comment Threads | Keyboard Shortcuts

  • ThomasPowell 7:21 pm on April 30, 2014 Permalink
    Tags: , enum, , rails 4.1   

    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.

     
  • ThomasPowell 8:10 pm on April 29, 2014 Permalink
    Tags: , has_many,   

    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
    
     
  • ThomasPowell 4:09 pm on April 29, 2014 Permalink
    Tags:   

    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.

     
c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
Reply
e
Edit
o
Show/Hide comments
t
Go to top
l
Go to login
h
Show/Hide help
shift + esc
Cancel
%d bloggers like this: