Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to set some attributes of Profile model is public(visible) or private(invisible) to other user?

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.

like image 736
Sachin Singh Avatar asked Oct 07 '14 13:10

Sachin Singh


4 Answers

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
like image 186
Diego Plentz Avatar answered Nov 06 '22 03:11

Diego Plentz


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? %>
like image 39
Surya Avatar answered Nov 06 '22 03:11

Surya


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
like image 2
JPrevost Avatar answered Nov 06 '22 03:11

JPrevost


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.

like image 1
yutu Avatar answered Nov 06 '22 03:11

yutu