So I have an application with users/user-profiles. Presently, users can view others profiles, send messages, and favorite user profiles. I'd like to also add the feature to allow users to 'hide-user' profiles so that they won't see the user ever again in search, or anything. And provide the option in 'settings' to 'un-hide' the user as well.
How might I do this? I haven't a clue as to where to begin with this feature.
(If you need any code examples, models, controllers please ask and I will happily provide)
Kind regards,
Sonny
There are probably a couple of ways to do this, the first approach that comes to mind would be to establish a self-referencing many to many relationship.
You will need to create the join table (I shall call it suppressed_users). I will show the rails model, as the migration wouldn't have anything other than the foreign keys.
class SuppressedUser < ActiveRecord::Base
belongs_to :user
belongs_to :suppressed_user, :class_name => "User", :foreign_key=>"suppressed_user_id"
end
And in the User model, in order to help DRY up your code, you can use a scope to easily filter all the users that this target user has decided to suppress (or hide):
class User < ActiveRecord::Base
has_many :suppressed_users // Optional
scope :without_hidden_users, -> (target_user) do
where.not("exists (?)",
SuppressedUser.select("1")
.where("users.id = suppressed_users.suppressed_user_id AND suppressed_users.user_id = ?", target_user))
end
end
Note about the scope: What I'm doing here is creating a dependent (or correlated) subquery, in which I check whether the target user has suppressed (or hidden) the user we're looking at at the moment. In other words, the dependent subquery is executed for each row in the users result set (those not filtered by other where or join conditions and such). With proper indexing, this should not have an impact on performance.
Note about has_many :suppressed_users: This is technically not needed for the query I've shown, so if it is not relevant for anything in your system, you should be safe to remove it.
So, if I am presently logged in, and I want to search for a list of users meeting some condition, in your controller you would do something like this:
User.without_hidden_users(#current_user.id)...other conditions and such as needed
Assuming #current_user represents the currently logged in user.
I believe one approach would be to create a many_to_many relationship via the has_many :through association between users and hidden users. And likewise another many_to_many relationship between users and hiders (i.e. users who are hiding the user). In the end, you should be able to do something like this:
some_user.hidden_users and some_user.hiders (i.e. all the users that are hiding some user). You probably won't need the second one most of the time.
So the first thing to do would be to create a join table called hiders_hidden_users (or whatever you want) which would contain only two fields: hider_id and hidden_user_id.
Note, that in reality, those ids will both refer to a User record. Your HidersHiddenUser model will look something like this:
class HidersHiddenUsers < ActiveRecord::Base
belongs_to :hidden_user, class_name: "User", foreign_key: "hidden_user_id"
belongs_to :hider, class_name: "User", foreign_key: "hider_id"
end
Then, you need to set up the relationships in the User class:
class User < ActiveRecord::Base
has_many :hiders_join, class_name: "HidersHiddenUser", foreign_key: "hider_id"
has_many :hidden_users_join, class_name: "HidersHiddenUser", foreign_key: "hidden_user_id"
has_many :hiders, through: :hiders_join
has_many :hidden_users, through: :hidden_users_join
end
Note, that you have to specify the class name and foreign key when writing the has_many relationship with the join table. Note, also that you have to specify the relationship twice with the same model.
Then, say some_user wants to hide user1 and user2. All you would need to do is something like this:
some_user.hidden_users << user1
some_user.hidden_users << user2
Although I have not tested this, I believe it should work.
Related
I may be going about this the wrong way but after reading various SO articles and the Rails docs on associations and scopes, I'm not much wiser.
I have a many-to-may relationship expressed like so:
class User < ActiveRecord::Base
has_many :user_program_records
has_many :programs, through: :user_program_records
end
class Program < ActiveRecord::Base
has_many :user_program_records
has_many :users, through: :user_program_records
end
class UserProgramRecord < ActiveRecord::Base
belongs_to :user
belongs_to :program
# has a field "role"
end
The idea is that there are many users in the system and many programs. Programs have many users in them and users may belong to multiple programs. However - within a given program, a user can only have one role.
What I'd really like to be able to write is:
Program.first.users.first.role
and have that return me the role (which is just a String).
What's the cleanest way to do this? Basically, once I've scoped a user to a given program, how do I cleanly access fields on the relevant join table?
You are thinking about it slightly wrong:
user.role
Would be very ambiguous as a user can have different roles in different programs. Instead you need to think of the join entity as a thing of its own.
The easiest way is to select the join model directly:
program = Program.includes(:user_program_records, :users).first
role = program.user_program_records
.find_by(user: program.users.first)
.role
You can use stuff like association extensions and helper methods to make this a bit sexier.
In my Rails app, I have the following models:
class User < ActiveRecord::Base
# user declaration
has_many :account
end
class Account < ActiveRecord::Base
belongs_to :user
end
I need to add another relation where each Account can be managed by multiple AccountManagers. However, AccountManagers themselves are also Users of this system (i.e. a User can both have an Account and manage another user's account).
I'm fairly new to Rails, and I know that I can just create another model called AccountManagers. I have a feeling that we don't NEED to make another model however; all the information contained within my proposed AccountManagers model is found in the Users model as well.
I've tried to add the following relationship to the Account model:
has_many :account_managers, through: :users, source: :users
where the Account has many managers, and each manager is declared from the User model. This doesn't work as the AccountManagers table doesn't exist (and the error in the view states that as well).
Is there a way to get this relationship to work?
You have to add another join table, say account_managements with columns and user_id and account_id
class User < ActiveRecord::Base
# user declaration
has_many :accounts
has_many :managed_accounts, :source => :account, :through => :account_managements
end
class Account < ActiveRecord::Base
belongs_to :user
has_many :account_managers, :source => :user, :through => :account_managements
end
and
class AccountManagement < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
You don't need to add another class AccountManager, You can add one more relation like this:
class User < ActiveRecord::Base
# user declaration
has_many :account
has_many :managed_accounts, :class_name => 'Account', :primary_key => :account_manager_id
end
class Account < ActiveRecord::Base
belongs_to :user
belongs_to :account_managers, : class_name => :User, :foreign_key => :account_manager_id
end
For this you will need to have account_manager_id in the accounts table.
The main issue I can see here is that a user has_many accounts, but in ur example, an account only has 1 user. Are u planning to have accounts with only 1 person in them?
In relational tables there are 2 main kinds of table designs.
A. One to Many
U have a relationship where one thing has many of another thing, for instance: a Owner has many Cats. In this type of relationship the Cat table can point to the Owner table. Clearly the Owner cannot point to its cats because u would need to have a pointer for each cat. it would be something like
Owners table
cat_1
cat_2
cat_3
thats not convenient at all, since there is a maximum number of cats.
If on the other hand u have
cats table
owner_id
then each owner can have an unlimited amount of cats.
More complex
After some time u get a request, some owners share a cat. u think about it a while and decide a cat can only belong to a max of 2 owners.
so u change the cat table.
cats table
owner_1
owner_2
B. Many to Many
If you want to be able to allow a cat to have any unlimited number of owners, or a owner to have an unlimited number of cats, u will need a third table. This table keeps a link from 1 cat to 1 owner. Each row defines this relationship. This table can be named either using the names of the other 2 tables: cat_owners or since this table defines a relationship u can give it a name that defines this relationship.
In this case the relationship is Ownership, people own cats. Its hard to say how the cats would define the relationship :)
This brings me to answer ur question finally. If u have a table that defines the link between a user and an account, I like the name Membership
Membership
user_id
account_id
but as u just mentioned there are some users in an account who are Managers, u can add this flag to the membership table.
Membership
user_id
account_id
manager (true/false)
u could take it a step beyond that and change manager, to a role column, and then every user can have a different role in every account.
What if each user can have multiple roles in each account? Then u will need more tables
Rails doesnt mean u can skip understanding relational databases structure.
I have the following models:
Account
has_many :libraries
Library
has_many :topics
belongs_to :account
Topic
has_many :functions
belongs_to :library
Function
has_one :example
belongs_to :topic
Example
belongs_to :function
I would like to be able to able to do things such as:
some_account.libraries
some_account.topics
some_account.functions
some_account.examples
In addition, I would like to be able to assign an account to a descendant, i.e
some_example.account = some_account
some_function.account = some_account
some_topic.account = some_account
some_library.account = some_account
To give some context:
I am letting a user (Account) create each Library, Topic, Function, Example. record separately. Then a user is free to change how the records are associated: Change the topic of a Function, move a Topic to a different Library, add an example to a function, and so on.
To my understanding no matter what record is created, I would need to assign it to a user (account) so that I can have a list of each Model records that a user has created, as well as prevent other users from seeing stuff that doesn't belong to them
Although I might be overcomplicating, I really don't know :(
Thanks in advance.
Just put
belongs_to :account
on each entity a user can make... and add a foreign key, and
Account
has_many :libraries
has_many :topics
has_many :functions
has_many :examples
(Note: I use the hobo_fields gem to make migrations easier)
That way.. if they change which functions are in which topics etc.. you can't loose who created it.
If you want to make sure users cannot add their topics to someone else's library just put validation on the record to prevent it.
I'm creating a little social network in Rails, where people can add eachother as a friend. I have created a model called 'user', which contains, e-mail, strong md5 hash with salt of password etc.
How do I create something like an option to add another user as a friend? Is it possible to have something like has_many_and_belongs_to :user in the user model? So a user has many users and belongs to many users. Or should I use another way, like adding a friendship model which has user1s_id:integer and user2s_id:integer?
Essentially you want a join model that joins users to users. But you'll have to use more descriptive terms for Rails.
Twitter doesn't use Rails, but here's how their user associations might work in Rails using Twitter's terminology. Note: Twitter doesn't require bidirectional following (ie just because a user follows another, doesn't mean that second user follows the first)
class User < ActiveRecord::Base
has_many :followings
has_many :followers, :through => :followings, :class_name => "User"
has_many :followees, :through => :followings, :class_name => "User"
end
class Following < ActiveRecord::Base
# fields: follower_id followee_id (person being followed)
belongs_to :follower, :class_name => "User"
belongs_to :followee, :class_name => "User"
end
Forced bidirectional friendship (like Facebook enforces that I cannot be friends with you, unless you are a friends with me) will take a little more work. You will either need to manage reciprocal entries with callbacks, or use custom finder SQL. Using custom finder SQL means that ActiveRecord probably won't be able to manage associations for you.
I would suggest that a user has many relationships, this would leave you free to add new types of relationships in the future.
So you start with a "one user to another user" relationship table (minimally, just two IDs) and then in the future you could add a "one user to a group" relationship table (once you've created groups of course!)
I'd suggest using the friendship model because db-wise you'll need a join table either way, but making that table explicit will allow you to store more details about the relationship (e.g. "friend" or "family", date added, ...)
Rails has a has_one :through association that helps set up a one-to-one association with a third model by going through a second model. What is the real use of that besides making a shortcut association, that would otherwise be an extra step away.
Taking this example from the Rails guide:
class Supplier < ActiveRecord::Base
has_one :account
has_one :account_history, :through => :account
end
class Account < ActiveRecord::Base
belongs_to :supplier
has_one :account_history
end
class AccountHistory < ActiveRecord::Base
belongs_to :account
end
might allow us to do something like:
supplier.account_history
which would otherwise be reached as:
supplier.account.history
If it's only for simpler access then technically there could be a one-to-one association that connects a model with some nth model going through n-1 models for easier access. Is there anything else to it that I am missing besides the shortcut?
Logic, OK it might sound a bit weak for this but it would be logical to say that "I have a supplier who has an account with me, I want to see the entire account history of this supplier", so it makes sense for me to be able to access account history from supplier directly.
Efficiency, this for me is the main reason I would use :through, simply because this issues a join statement rather than calling supplier, and then account, and then account_history. noticed the number of database calls?
using :through, 1 call to get the supplier, 1 call to get account_history (rails automatically uses :join to retrieve through account)
using normal association, 1 call to get supplier, 1 call to get account, and 1 call to get account_history
That's what I think =) hope it helps!
I'm surprised no one has touched on Association Objects.
A has_many (or has_one) :through relationship facilitates the use of the association object pattern which is when you have two things related to each other, and that relation itself has attributes (ie a date when the association was made or when it expires).
This is considered by some to be a good alternative to the has_and_belongs_to_many ActiveRecord helper. The reasoning behind this is that it is very likely that you will need to change the nature of the association or add to it, and when you are a couple months into a project, this can be very painful if the relationship were initially set up as a has_and_belongs_to_many (the second link goes into some detail). If it is set up initially using a has_many :through relationship, then a couple months into the project it's easy to rename the join model or add attributes to it, making it easier for devs to respond to changing requirements. Plan for change.
Inverse association: consider the classic situation user-membership-group. If a user can be a member in many groups, then a group has many members or users, and a user has many groups. But if the user can only be a member in one group, the group still has many members: class User has_one :group, :through => :membership but class Group has_many :members, :through => memberships. The intermediate model membership is useful to keep track of the inverse relationship.
Expandability: a has_one :through relationship can easy be expanded and extended to a has_many :through relationship