soveran / ohm

Object-Hash Mapping for Redis
http://ohm.keyvalue.org
MIT License
1.4k stars 167 forks source link

Tagging example #215

Open scalp42 opened 7 years ago

scalp42 commented 7 years ago

In the tagging example, we can find posts having a certain tag with this:

Post.find(tag: 'tagging')

Now how would someone be able to get the posts matching a tag (pretty much reversed logic) ?

I'm assuming you would need a reference in the Tag model to the Post model but I can't quite figure it in the Ohm way:

class Tag < Ohm::Model
  counter :total
  reference :posts, :Post

# How would you query Tag model after that to find the posts associated ?

Any chance it could be added to the example ?

Thanks a lot!

soveran commented 7 years ago

@scalp42 I don't use a Tag model, so for me if I have a tag t, I do Post.find(tag: t) as you described. What kinds of features do you expect a Tag model to provide? I see you want to have counters, for example, and a reference to Post. Would it be polymorphic? What about new tags: are users allowed to create new tags or they are created by you beforehand? There are ways to implement all those features, but each approach is slightly different. Let me know.

scalp42 commented 7 years ago

I think I'm trying to figure the many-to-many relationship with Ohm in the case of Post having multiple Tag and Tag belonging to multiple Post if it make sense ?

Thanks a lot for the quick answers as always, greatly appreciated.

soveran commented 7 years ago

@scalp42 Looking back at this issue, did you find a way to do what you needed? If not, let me know and I'll work on an example.

scalp42 commented 7 years ago

Let's assume a vendor (Google), having multiple users subscribed to its paying products (Youtube, Music, etc), the models would like like this:

class User < Ohm::Model
  attribute :name
end

class Subscription < Ohm::Model
  reference :user, :User
  reference :product, :Product
end

class Product < Ohm::Model
  attribute :name
  reference :vendor, :Vendor
end

class Vendor < Ohm::Model
  attribute :name
  collection :products, :Product
end

Now let's create an user, a vendor and a product to match the description above:

user = User.create(name: 'john@example.com')

vendor = Vendor.create(name: 'google')

product = Product.create(name: 'youtube', vendor: vendor)

Now we want to have our user John subscribed to Youtube:

Subscription.create(user: user, product: product)

Let's assume that we have dozen of users, vendors, products and subscriptions that go with it. We can query our "join table" to get the exact info we need:


# This would return all the users subscribed to the "youtube" product
Subscription.find(product_id: product.id).map(&:user)

# This would return all products that a particular user "John" is subscribed to:
Subscription.find(user_id: user.id).map(&:product)

# Which then could also returns all the vendors user "John" is subscribed to:
Subscription.find(user_id: user.id).map(&:product).map { |p| p.vendor.name }
=> ["google"]

Couple extras to help understand references relation:

Product.all.first
=> #<Product:0x007fa303991698 @_memo={}, @attributes={:type=>"youtube", :vendor_id=>"1"}, @id="1">

Subscription.find(user_id: user.id).first
=> #<Subscription:0x007fa3033a3bf0 @_memo={}, @attributes={:user_id=>"1", :product_id=>"1"}, @id="1">

Subscription.find(user_id: user.id).first.product
=> #<Product:0x007fa303307a48 @_memo={}, @attributes={:type=>"youtube", :vendor_id=>"1"}, @id="1">

Product.find(vendor_id: vendor.id).first
=> #<Product:0x007fa3032bea50 @_memo={}, @attributes={:type=>"youtube", :vendor_id=>"1"}, @id="1">

Product.find(vendor_id: vendor.id).first.vendor
=> #<Vendor:0x007fa3031b4d58 @_memo={}, @attributes={:name=>"google"}, @id="1">

Product.find(vendor_id: vendor.id).first.vendor.products.first
=> #<Product:0x007fa30357b1a8 @_memo={}, @attributes={:type=>"youtube", :vendor_id=>"1"}, @id="1">

Vendor.all.first
=> #<Vendor:0x007fa30190f1b8 @_memo={}, @attributes={:name=>"google"}, @id="1">

Vendor.all.first.products.first
=> #<Product:0x007fa303aa1b50 @_memo={}, @attributes={:type=>"youtube", :vendor_id=>"1"}, @id="1">

One thing to note is that when you write:

class Subscription < Ohm::Model
  reference :user, :User
  reference :product, :Product
end

The reference actually creates an index that you can query with an id hence:

Subscription.find(product_id: product.id)

Hopefully it helps others @soveran, thanks again for the help and sorry for the delay!

soveran commented 7 years ago

@scalp42 That's excellent! Thanks!