I have three models, User, Product, Like. That is, a user can (un)like a product...
I wonder what would be best practice to design controller and models.
Creating likes_controller and process create/destroy actions
Creating like/unlike actions inside products_controller
or something else.
Second question; into which model should I put methods such as "retrieve_likes," "liked?," or "like! /unlike!"
Edit: I found this excellent gem through TeachMeToCode.com, https://github.com/cavneb/make_flaggable
I've done both option 1 and 2 before and they've both worked fine. It really depends on your philosophy and how much of the REST kool-aid you've drank. Go with #1 to be RESTful. I've found sticking to a more RESTful interface makes testing seem a bit cleaner and the code, in general, seem a bit cleaner. At the expense, however, of having way more files.
As for the models, you could create a Likable module which contains the methods for "liked?", etc... Then you can include this module in any of the models which need them. Take a look at some gems that extend ActiveRecord models to get an idea how to do that.
You could have 3 tables:
users: user_id, name, like_id
products: product_id, info, like_id
likes: like_id, user_id, product_id
In your User model:
has_many :likes
In your Product model:
has_many :likes
In your Like model:
belongs_to product
belongs_to user
This would allow you to do things like call <%= #product.likes.count %> for a count of the number of times something was liked.
Or in the users controller: #liked_products = User.likes.map(&:product).
To create links that populate the likes table, you will need to make a likes controller with the add_like and remove_like methods.
You may also need to learn about something called "nested routing" to get it all working.
Related
This is probably a really simple question, but I've been searching the web for probably around an hour and I can't really find an answer to my problem. It should be clear by what follows that I am very new to Rails, so my terminology and explanation might be a bit confusing.
Let's say that I were making a social media app on Rails, where one of the models is User. I want to make a many-to-many relationship called "friends", which links two users together. Let's say in this situation I also wanted to make a many-to-many between two users called "enemies".
This is all completely hypothetical, but the idea is the same one that I want to use for something I'm working on.
Because a user can have many friends and enemies, but also be many friends and enemies, I would use:
class User < ActiveRecord::Base
has_and_belongs_to_many :users #this should be the friends association
has_and_belongs_to_many :users #this should be the enemies association
end
Now I'm guessing I can't just do that, because I would have to have two tables both named users_users. So, then I switch to:
class User < ActiveRecord::Base
has_and_belongs_to_many(:users, join_table: 'friends',
foreign_key: 'user_id', associate_foreign_key: 'friend_id')
end
With a similar statement for the enemies table. Now, my problem is that I want to have a form that the user can use when they sign up, where they can input their information (this is the User object details), and also list their friends and enemies.
Because the user won't have the database id key for their friends or enemies, they'll have to input the users' names. This is fine, though because the name is also a unique key, guaranteed by the validation.
However, if the user types in the name of a friend, I can't join the two if the friend happens to not exist. So, I use a custom validation class that looks something like this:
class FriendValidator < ActiveModel::Validator
def validate(object)
#lookup user and throw error if not found.
end
end
which will access the variable (object.friends) and (object.enemies)
With something similar for enemies. So therefore, above my has_and_belongs_to_many statements, I have lines that say:
attr_accessor :friends, :enemies #these are attrs because they don't exist within the model's db
validates_with FriendValidator
When I create the form with erb, I have the standard form_for block
<%= form_for(#user) do |f| %>
It seems to me that I can't just stick
<%= f.text_area :friends %>
because friends isn't actually something that will get passed to the User object, but rather a separate table. (Can I, though? Because the attr_accessor is declared in the user's model class?)
So now, we have my main problem. I have two many-to-many tables with a model to its own model class, and I don't know how to ensure that the validation class will take the two attributes, lookup and throw necessary errors, and then add a row to the join tables using the id of the user, rather than the string inputted. What form fields should I use to pass the input to the right place? Where do I change the controller methods so that the input gets sent to the join table rather than the user object?
This definitely seems like a pretty specific situation, so I can't really find an answer in the Rails documentation, which I've been learning from.
My initial impression of this problem has to do with your associations. To me, a user has_many enemies and has_many friends.
friends belong_to user
enemies belong_to user
Not sure if a many to many relationship makes sense in this case. Maybe that's why you are having such a hard time finding an answer online. Just my two cents.
The question below had a good answer to grab associated values of an activerecord collection in one hit using Comment.includes(:user). What about when you have multiple associations that you want to grab in one go?
Rails have activerecord grab all needed associations in one go?
Is the best way to just chain these together like below Customer.includes(:user).includes(:sales).includes(:prices) or is there a cleaner way.
Furthermore, when I am doing this on a loop on an index table. Can I add a method on the customer.rb model so that I can call #customers.table_includes etc and have
def table_includes
self.includes(:user).includes(:sales).includes(:prices)
end
For the record I tested the above and it didn't work because its a method on a collection (yet to figure out how to do this).
In answering this, I'm assuming that user, sales, and prices are all associations off of Customer.
Instead of chaining, you can do something like this:
Customer.includes(:user, :sales, :prices)
In terms of creating an abstraction for this, you do have a couple options.
First, you could create a scope:
class Customer < ActiveRecord::Base
scope :table_includes, -> { includes(:user, :sales, :prices) }
end
Or if you want for it to be a method, you should consider making it a class-level method instead of an instance-level one:
def self.table_includes
self.includes(:user, :sales, :prices)
end
I would consider the purpose of creating this abstraction though. A very generic name like table_includes will likely not be very friendly over the long term.
I have groups (Group model) in my app, which represent groups of people.
I want each group to have its own forum.
Should I just have the forum id in the groups table? It doesn't feel right. If I did it myself, the forum would have a polymorphic association to a "forumable" element (groups in this case, but I have other models that would need a forum).
Any opinions on what I should do? Modify the gem to fit my needs, or just have the forum_id in my models that need a forum? Or another solution maybe?
I'm the guy who started Forem (its the volunteers who did most of the hard work, though!), I think I can answer this question.
If you want only certain groups to have access to one and only one forum then you can put the forum_id field on the groups table and do it that way. What you can do then is override the can_read_forem_forum? method in your User model to perform a permission check for that user:
def can_read_forem_forum?(forum)
groups.where(:forum_id => forum.id).any?
end
This is used in Forem's ability model to determine whether or not a person can access a forum. What this method is going to do is that it will only return groups for that user that have link that specific forum. If there are any, then it's known that the user can access that forum.
Now if you're going the other route where a group may have access to many forums, well then you'd define a joins table between groups and forem_forums (called forum_groups) and define it as an association in your Group model like this:
has_many :forum_groups
has_many :forums, :through => :forum_groups, :class_name => "Forem::Forum"
You would need to also define a new model inside your application for this forum_groups association, it would be called ForumGroup and go a little like this:
class ForumGroup < ActiveRecord::Base
belongs_to :forum, :class_name => "Forem::Forum"
belongs_to :group
end
We're doing it this way so you have an easy way to manage the associations between forums and groups. If you did has_and_belongs_to_many, it generally only provides a gigantic pain in the ass when you want to delete one specific record from that join table.
Now, with that all nicely set up, the method you want to define in your User model is this one:
def can_read_forem_forum?(forum)
groups.joins(:forums).where("forem_forums.id = ?", forum.id).any?
end
Same thing, except this time we find all the groups that are linked to a specific forum through that association we set up earlier. This will do an INNER JOIN on the forum_groups table, and then another on the forem_forums table, getting the data required.
I hope this helps you, and thanks for using Forem!
In Rails, I want to override the behavior of an association. For example, by default, if Person has_many :hats, calling some_person.hats would do a simple join using person.id and hat.person_id.
I want to modify that query to include some other criteria. For example, maybe a person's collection of hats should be just the hats that are appropriate to their country.
It seems that I could do something like this:
class Person < ActiveRecord::Base
has_many :hats, :through => :country do
# John lives in Canada, so he gets a baseball cap and a hockey helmet
self.country.hats
end
end
Can I control what an association returns like this? If not, would a scope be the best solution?
I know this is a silly example, but explaining the domain logic that I need this for would be way too boring for everyone here. :)
Scopes are probably your best option because they're chainable and reusable outside your association. Otherwise, you could use association extensions. Check out this thread for more info. Association Extensions
Suppose I have 3 models, Car, Motorcycle and Truck, and for each I have to enter a bunch of stuff, such as a list of known previous owners, traffic tickets, license plates, etc. So I created a model for each (PreviousOwners, PreviousPlates, etc) and set up polymorphic associations for the related models.
The problem is, how can I enter all of that using just one form, kind of like this:
Car #123
Known previous owners:
Jason Jazz
Brian Bass [add another]
Known previous license plates:
12345
67890 [add another]
Current status:
Cleared
(this is a dropdown select menu, CurrentStatus is also a polymorphic association, but with predefined values.)
etc
This is proving to be a bitch, way beyond my level of expertise (newbie here). The resources are not nested and almost everything I find on multiple models is for nested resources, and nothing seems to apply to polymorphic associations.
(This is just an example, I know ideally I should have a Vehicle model with 'Car', etc, as categories, but it's just to illustrate the real need for polymorphic models in my case.)
Thanks.
Maybe the PresenterPattern is helpfull too:
http://blog.jayfields.com/2007/03/rails-presenter-pattern.html
The basic idea is to create a presenter which acts like a model and processes all the incoming data from your form and distributes it to the models. This way it's also easy to create multiple instances of lets say PreviousOwner and attach it to Car.
Check the link out!
You can use the new nested attributes in Rails 2.3, but there is a certain way you have to write it to make it work. The trick is that you need to create the actual polymorphic object, then build the class that has the belongs to polymorphic clause in it. This is an example I found at Ryans Scraps, posted by a user named: Superslau (I've cleaned it up a good bit for here):
This feature is really awesome. I have
implemented this with polymorphic
associations, and it works!
class Task < ActiveRecord::Base
has_many :assets, :dependent=>:destroy
accepts_nested_attributes_for :assets, :allow_destroy => true
belongs_to :workable, :polymorphic => true
end
class Upload < ActiveRecord::Base
has_one :task, :as => :workable, :dependent=>:destroy
accepts_nested_attributes_for :task, :allow_destroy => true
end
Upload is a kind of task. All tasks
can have one or more assets uploaded.
I took me a while to figure out that I
should use the Upload model as the
parent. So in one form, I can create
an upload, and it’s corresponding task
entry, along with a file upload.
in my controller:
def new
#upload = Upload.new
#upload.task = Task.new
#upload.task.assets.build
end
Don’t
worry if that doesn’t make any sense,
I just wanted to let people know that
accepts_nested_attributes_for works
just fine with polymorphic
associations. Thanks Eloy!
Very well, nested form builders doesn't have to be associated with nested resources AFAIK.Can you post your models code as well?
There is a RailsCast on Complex Forms that might help you with building a single form from multiple models.
If the car/motorcycle/truck models are identical, you should add a type column to your vehicle model. If they're not, you should use STI (single table inheritance).
But yeah, need to see your models first before I can give you code.
You can avoid this and make things a bit simpler by introducing a Vehicle model. The Vehicle model can have all your PreviousOwners, PreviousPlates, etc collections, and then your Truck, Car and Motorcycle models can has_one Vehicle.