Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulating has_many :through with Mongoid

I'm trying to create an event platform using MongoDB as the db. I want a many-to-many relationship between Events and Users. The thing is, I want there to be properties in the relationship (e.g., Users can either be confirmed or unconfirmed for a specific Event). I realize this would be ideally suited for an RDBMS, but I'm using MongoDB for reasons that I'm taking advantage elsewhere and I would prefer to continue using it.

What I would like is for each Event to embed many Guests, which belong to Users. That way, I can see which users are attending an event quickly and with only one query. However, I would also like to see which Events a User is attending quickly, so I would like each User to have an array of Event ids.

Here is a code summary.

# user of the application
class User
  has_many :events
end

# event that users can choose to attend
class Event
  embeds_many :guests
  has_many :users, :through => :guests      # Won't work
end

# guests for an event
class Guest
  field :confirmed?, type: Boolean

  embedded_in :event
  belongs_to  :user
end


# Ideal use pattern
u = User.create
e = Event.create
e.guests.create(:user => u, :confirmed => true)

With the ideal use pattern, e has a Guest with a reference to u and u has a reference to e.

I know the has_many :through line won't work. Any suggestions as to how to get similar functionality? I was thinking of using an after_create callback in Guest to add a reference to the Event in User, but that seems pretty hacky.

Maybe I've gone down the wrong path. Suggestions? Thanks.

like image 808
cbrauchli Avatar asked Sep 13 '11 00:09

cbrauchli


2 Answers

You can just store the event ids in a array on the user.

You have to manage the array when the event changes or the user is removed from the event for some reason. But that is the trade off.

User.events can then be found with a single db call.

Look at observers to manage the association.

like image 165
nodrog Avatar answered Nov 15 '22 18:11

nodrog


I ended up using callbacks in the models to accomplish I wanted. Here's what it looks like.

Edit: I just saw nodrog's answer. Yeah, using observers would probably have been neater, I didn't know about them. Thanks!

# user of the application
class User
  has_and_belongs_to_many :events, inverse_of: nil, dependent: :nullify
end

# event that users can choose to attend
class Event
  embeds_many :guests
  index 'guests.user_id', unique: true
  before_destroy :cleanup_guest_references

  def cleanup_guest_references
    self.guests.each do |guest|
      guest.destroy
    end
  end
end

# guests for an event
class Guest
  field :confirmed?, type: Boolean

  embedded_in :event, :inverse_of => :guests
  belongs_to  :user

  after_create :add_event_for_user
  before_destroy :remove_event_for_user

  private
    def add_event_for_user
      self.user.events.push(self.event)
    end
    def remove_event_for_user
      self.user.events.delete self.event
      self.user.save
    end
end
like image 3
cbrauchli Avatar answered Nov 15 '22 18:11

cbrauchli