简体   繁体   中英

Rails Associations: HABTM?

Hey guys, I'm at a deadlock here after thinking about this for too long.

Context: Given the following models:

  • User
  • Item
  • Lock

Here's the scenario: A lock is basically like a 'hold'. A user can place a 'lock' on any given item to signal to the system that the item should not be deleted. Items wont be deleted until the lock is cleared.

Here's the tricky part. The lock is its own model because I want multiple users to be able to lock any given item. So let's say Bob locks an item, one didn't already exist so it creates a lock for that item, and information stating that Bob is currently associated with that lock. John comes and locks the same item, but a lock already exists, so John is simply 'added under' the same lock. The lock won't be removed until all users choose to 'unlock', or disassociate themselves with that lock.

My confusion is how I should model these relationships. A user can of course have many locks, each associated with a different item (since any given item can have at most one lock). The locks themselves can have many users. From the point of view of the item, each item can have one lock associated with many users.

So in other words, I would like to access the information a little something like this:

item.lock.users # get the users 'locking' the item
user.locks # get the items the user is currently 'locking

Perhaps the separate Lock model isn't required, but I figured it would be in order to signify that multiple users can be locking a particular item.

I think what further complicates things is that items are added by users, so I would want to have a way to access the items by a user for example user.items or item.user .

Right now I have:

  • user has and belongs to many locks
  • lock has and belongs to many users
  • user has many items
  • item belongs to user
  • item has one lock
  • lock belongs to item

Does this seem correct?

I think what you're doing will work though you may not have to use the habtm. What if an item can have many locks and can only be deleted when it has no locks. That way you could add a date/reason/comment for each lock by user.

  • user
    • has_many :locks
    • has_many :items
  • lock
    • belongs_to :user
    • belongs_to :item
  • item
    • belongs_to :user
    • has_many :locks

This will still allow you to do user.locks though item.lock.users won't work, but by looking at each lock you'll easily be able to get the users.

item.locks.each do |lock|
    puts lock.user
end

You don't need the lock model. You can simply set up a habtm relationship between users and items:

class User < ActiveRecord::Base
  has_many :items
  has_and_belongs_to_many :locks, :class_name => "Item"
end

User.first.items # => [<#Item>, <#Item>, ...] # Items created by user
User.first.locks # => [<#Item>, <#Item>, ...] # Items locked by user

class Item < ActiveRecord::Base
  belongs_to :user
  has_and_belongs_to_many :lock_users, :class_name => "User"
end

Item.first.user # => <#User> # Creator of the item
Item.first.lock_users # => [<#User>, <#User>, ...] # "Lockers" of the item

You'll have to create a join table, of course, and be mindful of what Rails expect the join table to be named. You may be better off specifying the :join_table option for the habtm.

The key here is that relationships in Rails are very flexible. You can have multiple relationships between the two tables; you can have both the 'created by' and the lock relationships, independently of each other. All you have to do is use different names for the relationships.

I can relate to "thinking about this for too long". When that thought creeps into my mind I back away and work on other parts of the code. Relationships seem to reveal themselves over time as they're really a convenience to spare us writing a bunch of code. They're not essential, so, at least during the development phase, we can postpone the declaration of the relationships and see what we need a bit later.

Yeah, we're supposed to always know ahead of time according to the pragmatists, but in real life we're often having to use our common sense and experience and build a working prototype, then fine tune it. It's during that fine-tuning stage I tweak my relationships that weren't exactly clear before, and adjust my code.

Sniff... sniff... jeez, now my bosses know I'm not perfect... sniff....

Back to the question at hand: Normally, for locks to prevent accidental (or on purpose) deletion, I create a boolean field in my main table and if that record should be purged set it to true. For what you're doing I'd probably get rid of the flag field altogether and have a separate table that is the IDs of the records to lock, along with the user's IDs who want to keep the record. Delete those user's records if/when they think it's time to delete the record. When it's time to do some purging I'd check against that table. Something similar to:

delete from table1 where id not in (select distinct(table1_id) from table2)

The thing I don't like about it is there's potential to have another table full of "keep this record" records, and that's when I add another table for users to terminate who can't decide what things need to be deleted.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM