Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I define a relationship between activerecord and activeresource using foreign keys?

I have a local user model which uses activerecord. The user has an email field. I also have an activeresource model called tasks which has a created_by field which stores the submitting users email address. I'd like to link the two but I'm struggling with the right syntax or even whether it's possible.

The main branch of ActiveResource doesn't even seem to support foreign key. I found an alternative branch but still couldn't get anything working.

class User < ActiveRecord::Base
   has_many :tasks
end

class Task < ActiveResource::Base
  belongs_to :user
  schema do
    string 'created_by' #email
    # other fields
  end
end
like image 317
Simmo Avatar asked Jun 17 '14 08:06

Simmo


2 Answers

Your code should work fine given that you have a user_id attribute on the Task to act as the foreign key, or you can specify the foreign key in the association on User model as follows:

class User < ActiveRecord::Base
  has_many :tasks, foreign_key: "uid"
end

Now the problem is, you can't use belongs_to on ActiveResource so unless you need to retrieve the user from an instance of the Task class, you can just remove it and the other side of the relation will still work, however, if you need to retrieve the user then you'd have to either implement your own finder method as follows:

class Task < ActiveResource::Base
  schema do
    string 'created_by' #email
    # other fields
  end

  def user
    @user ||= User.find self.user_id # use the attribute name that represents the foreign key
  end

  def user=(user)
    @user = user
    self.update_attribute(:user_id, user.id)
  end
end

This will basically behave the same as you would expect on an ActiveRecord model, however this can be tiresome if you have multiple associations so you can instead extend the ActiveResource module to add belongs_to as follows:

module BelongsToActiveResource

    def self.included(base)
      base.extend(ClassMethods)
    end

    module ClassMethods

    def belongs_to( name, options = {} )
      class_eval %(
        def #{name}
          @#{name} ||= name.to_s.classify.find( name.id )
        end

        def #{name}=(obj)
          @#{name} ||= obj
          self.update_attribute((name.to_s + "_id").to_sym, @#{name}.id
        end
      )
    end

  end

end   

ActiveResource::Base.class_eval { include BelongsToActiveResource }

This will allow you to use belongs_to on any ActiveResource model.

P.S.: the above solution was inspired by https://stackoverflow.com/a/8844932/3770684

like image 140
cousine Avatar answered Nov 08 '22 22:11

cousine


You can't, but you can fake it by implementing accessor methods yourself.

class User < ActiveRecord::Base
  #has_many :tasks
  def tasks
    Task.find(:all, params: {created_by: email})
  end
end

class Task < ActiveResource::Base
  #belongs_to :user
  def user
    User.where(email: created_by).first
  end

  schema do
    string 'created_by' #email
    # other fields
  end
end

This will let you write code as if your objects were associated (i.e. User.first.tasks.length). However, they aren't actually joined. That means calling User.first.tasks is going to hit the database, then make an additional HTTP request to retrieve the Tasks. Depending on how your code is structured, you could run into unexpected performance issues.

Also, you can't run a single query to get a User and all associated Tasks (since it's two separate data stores), and you can't do fancy stuff like User.joins(:tasks).where({tasks: field_1: true}).

like image 36
James Mason Avatar answered Nov 09 '22 00:11

James Mason