Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I elegantly handle two-sided relations with Rails translations?

I have a family_tree and someone can add their relatives to the tree.

So what happens is there is a membership record created for each family_tree entry.

However, if a Son adds a Dad, we should be able to update the family tree of the Dad to add the "Son" to the tree in the view. What's the best Rails way to approach this? I know Rails does a lot of translations natively, and pluralizations, etc. Anyway for me to leverage that for what I want to do?

Also, what is the class/module that handles that stuff again? ActiveSupport?

This is my User model:

# == Schema Information
#
# Table name: users
#
#  id                     :integer          not null, primary key
#  email                  :string(255)      default(""), not null
#  encrypted_password     :string(255)      default(""), not null
#  reset_password_token   :string(255)
#  reset_password_sent_at :datetime
#  remember_created_at    :datetime
#  sign_in_count          :integer          default(0), not null
#  current_sign_in_at     :datetime
#  last_sign_in_at        :datetime
#  current_sign_in_ip     :string(255)
#  last_sign_in_ip        :string(255)
#  created_at             :datetime
#  updated_at             :datetime
#  name                   :string(255)
#  confirmation_token     :string(255)
#  confirmed_at           :datetime
#  confirmation_sent_at   :datetime
#  unconfirmed_email      :string(255)
#  invitation_relation    :string(255)
#  avatar                 :string(255)
#
class User < ActiveRecord::Base
  has_one :family_tree, dependent: :destroy
  has_many :memberships, dependent: :destroy
  has_many :nodes, dependent: :destroy
  has_many :participants, dependent: :destroy    
end

FamilyTree.rb

# == Schema Information
#
# Table name: family_trees
#
#  id         :integer          not null, primary key
#  name       :string(255)
#  user_id    :integer
#  created_at :datetime
#  updated_at :datetime
#

class FamilyTree < ActiveRecord::Base
  belongs_to :user
  has_many :memberships, dependent: :destroy
  has_many :members, through: :memberships, source: :user, dependent: :destroy
  has_many :nodes, dependent: :destroy
end

Membership.rb:

# == Schema Information
#
# Table name: memberships
#
#  id             :integer          not null, primary key
#  family_tree_id :integer
#  user_id        :integer
#  created_at     :datetime
#  updated_at     :datetime
#  relation       :string(255)
#

class Membership < ActiveRecord::Base    
  belongs_to :family_tree
  belongs_to :user      
end

Node.rb

# == Schema Information
#
# Table name: nodes
#
#  id             :integer          not null, primary key
#  name           :string(255)
#  family_tree_id :integer
#  user_id        :integer
#  media_id       :integer
#  media_type     :string(255)
#  created_at     :datetime
#  updated_at     :datetime
#  circa          :datetime
#  is_comment     :boolean
#

class Node < ActiveRecord::Base
  belongs_to :family_tree
  belongs_to :user
  belongs_to :media, polymorphic: true, dependent: :destroy
  has_many :comments, dependent: :destroy
  has_many :node_comments, dependent: :destroy    
end

My _tree.html.erb looks like this (truncated for brevity):

      <li class="tree-item-name"><a href="#">Great Grandparents</a>
        <ul>
          <li><% if relative.humanize == "Great Grandfather" || relative.humanize == "Great Grandmother" %>
            <%= link_to image_tag(membership.user.avatar.url, size: "48x48", :class => "img-circle") , family_tree_path(membership.user.family_tree), :target => '_blank' %>
                <%= link_to membership.user.name, family_tree_path(membership.user.family_tree), :target => '_blank'%>
              <% else %>
              None added yet, add them <%= link_to 'here', "#" , class: 'btn invite popupbox','data-popup' => 'invite_friend' %>
            <% end %>
          </li>
        </ul>
      </li>
      <li class="tree-item-name"><a href="#">Grandparents</a>
        <ul>
          <li><% if relative.humanize == "Grandfather" || relative.humanize == "Grandmother" %>
            <%= link_to image_tag(membership.user.avatar.url, size: "48x48", :class => "img-circle") , family_tree_path(membership.user.family_tree), :target => '_blank' %>
                <%= link_to membership.user.name, family_tree_path(membership.user.family_tree), :target => '_blank' %>
              <% else %>
              None added yet, add them <%= link_to 'here', "#" , class: 'btn invite popupbox','data-popup' => 'invite_friend' %>
            <% end %>
          </li>
        </ul>
      </li>
like image 883
marcamillion Avatar asked Oct 09 '14 21:10

marcamillion


1 Answers

I would use the same relations you defined in the question, except this part:

class Membership < ActiveRecord::Base    
  belongs_to :family_tree
  belongs_to :user_one, class_name: 'User'
  belongs_to :user_two, class_name: 'User' # I actually have no idea how to call them!
  belongs_to :relation # to hold values likes 'Son', 'Dad', etc.
  # The model Relation would be as simple as a name and internal reference, nothing else.
  # (internal_reference is here to solve the translation problems and other stuff you will understand with the following code)

With a callback after_create to reverse the membership created:

def create_reverse_membership
  user_one_is_female = user_one.gender == 'female'
  user_two_is_female = user_two.gender == 'female'
  son_or_daughter = user_one_is_female ? :daughter : :son
  father_or_mother = user_two_is_female ? :mother : :father

  case relation.internal_reference.to_sym
  when :son
    relation = Relation.find_by_internal_reference(father_or_mother)
    membership = Membership.where(relation_id: relation.id, user_one: user_two.id, user_two: user_one.id).first
    if membership.present?
      # This means the reverse membership already exists, do not call Membership.create here because it would cause and endless loop with the callback
    else
      membership = Membership.create(relation_id: relation.id, user_one: user_two, user_two: user_one)
    end
  when :father
    # almost same logic but with `son_or_daughter`
  when :mother
  else

  end
end

English not being my native language, this code probably lacks of consistency (coherence, logic).

Hope this helps!

like image 93
MrYoshiji Avatar answered Oct 10 '22 01:10

MrYoshiji