I have a Profile model and it has many attributes like email, image, age, address etc.
A end user can make some attributes private so that other users are not able to view that.
I solved this issue by adding a column to table private_attr
and serialized it to store a hash like:-
{email: true, address: true, age: false }
Here attributes as key having value true
are considered as private and not shown to the user other than to whom these belongs.
I want to know is this the best way to solve this problem, or there is any other way. Thanks in advance.
I think you can serialize just the fields that the user want to be private in an array(instead of a hash). Like [:email, :address]
(using your example)
Then, when you will render the view, just check if the field is present in that column. Something like
<%= user.email unless user.private_fields.include?(:email) %>
You can even extract that logic to a view helper to avoid duplication.
<%= show_if_allowed(user, :email) %>
Then create a helper like this
def show_if_allowed(user, field)
user[field] unless user.private_fields.include?(field)
end
Actually, that's what I would do if I had to deal with 8 to 10 attributes. But, if you have way too many attributes for your Profile
model class and have complex logic of showing these attributes on the basis of user's group, public, shared, etc. then I'd encourage you to move this to either a separate model class, let's say: "ProfileConfiguration
or ProfileSetting
", which will maintain each attribute on a row level, or you can move these settings to Redis, where the structure will be like: user_id: {attribute_name: true, type: 'type_name'}
but then there's a drawback that you'll be depending on Redis server's availability.
Now, in your case:
serialize :profile_preferences, Hash
and then you maintain it with(as you mentioned, just in the opposite manner for their assignment):
{email: false, address: false, age: true }
However, you can go ahead and create some handy methods you can call on your profile object:
after_initialize :load_profile_preferences
private
def load_profile_preferences
profile_preferences.each do |attr, value|
self.class.send(:define_method, "show_#{attr.to_s}?") { value }
end
end
Now, you get handy methods like: show_email?
, show_address?
, and show_age?
on Profile
class's object which you can delegate to User
class instance. So, you can now do something like this in your views, for example:
<%= "Email: {user.email}" if user.show_email? %>
<%= "Address: {user.address}" if user.show_address? %>
<%= "Age: {user.age}" if user.show_age? %>
You asked if there were other ways. Sure. Whether one is better is up to you.
Lots of Columns
You could create additional columns for each attribute. email_protected: true/false (etc). If you only have 3 columns, that's not too bad but wouldn't scale well if you are protecting lots of attributes.
Many to Many relationship
You could have an additional model, such as ProtectedAttribute. A Profile has_many ProtectedAttributes and store the data in separate tables instead of serializing. This eliminates the serialized data into a single column.
Additional Details follow as requested
# I'm using User instead of Profile as Profile is a protected word in Rails
class User < ActiveRecord::Base
has_many :profile_protected_attributes
has_many :protected_attributes, through: :profile_protected_attributes
def protect_attribute?(name)
protected_attributes.where(name: name).present?
end
def show_attribute?(name)
!protect_attribute(name)
end
def protect_attribute(name)
return if self.protect_attribute?(name)
protected_attributes << ProtectedAttribute.find_by_name(name)
end
def unprotect_attribute(name)
protected_attributes.delete(ProtectedAttribute.find_by_name(name))
end
end
class ProtectedAttribute < ActiveRecord::Base
has_many :profile_protected_attributes
has_many :users, through: :profile_protected_attributes
end
# The join model
class ProfileProtectedAttribute < ActiveRecord::Base
belongs_to :user
belongs_to :protected_attribute
end
The migrations (you'll need to tweak if you stick with Profile):
class CreateProtectedAttributes < ActiveRecord::Migration
def change
create_table :protected_attributes do |t|
t.string :name
t.timestamps
end
end
end
class CreateProfileProtectedAttributes < ActiveRecord::Migration
def change
create_table :profile_protected_attributes do |t|
t.integer :user_id
t.integer :protected_attribute_id
t.timestamps
end
end
end
You could use a gem named cancan to handle the rights your different kind of users you define in a ruby file (ability.rb
). Then, I suggest you to have may be different serializer for one resource, each serializer dedicated to a specific user role, it's easy to do with active-model-serializer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With