📜 ⬆️ ⬇️

How you can easily and easily implement options in Ruby on Rails

Prologue


image
Probably all the web (and maybe not only the web) developers sooner or later had to deal with such a task as user options . What I mean the easiest way to show in the picture. By options, we mean checkboxes (that is, the option is either there or not). Usually there are not too many such options, about 2-10. Usually they are indicated when registering and / or editing a profile.

Formulation of the problem


As a rule, the number of these settings (and the settings themselves) can vary, so it would be nice to implement the mechanism of these options in such a way that adding / changing / deleting them was as simple as possible while changing as little code as possible. I'll tell you how you can solve it and how I solved this task within the framework of the Ruby on Rails framework.

Implementation options


In general, I usually ask this question at the interview, I heard a lot of answers. One of them is to use two tables : one for options, the other for user-option communication (there is a record - it means there is a tick in the form).
Benefits:
Disadvantages:
Let me explain the second point: I would like to have something similar to:

   <% = f.check_box: send_mail_on_friend_request%>

Of course, this can be done, but the code in the model will not be trivial.
')
Another option for options is to use one column - the text, which stores the options in serialized form

 class User
   serialize: options, Hash
 end

In this example, it is unclear what the user’s options may be at all, let's improve:

 class User
   OPTION_1 =: key_1
   OPTION_2 =: key_2

   serialize: options, Hash

   def option_1 = (value)
     self.options [: key_1] = value
   end
   def option_2 = (value)
     self.options [: key_2] = value
   end
   def option_1
     self.options [: key_1]
   end
   def option_2
     self.options [: key_2]
   end
 end

Already clearer, but too many methods: what if there are 20 options? This problem is perfectly solved by define_method (an example of its use will be lower). But still, let's think: after all, an option, it’s just a checkbox, just a bit - either cocked or not (yes, yes, as a woman’s intuition :), why serialize, deserialize? Can it be made easier?

My implementation


Let's think how many such options can there be? I think a maximum of 10, well, 20, the more the user will not fill. And what if you keep the options as usual? Not a bad idea, just have to remember working with bits:

 class User <ActiveRecord :: Base
   NOTIFY_ON_MAIL = 0b0001
   NOTIFY_ON_WALL_POST = 0b0010
   NOTIFY_ON_FRIEND_REQUEST = 0b0100
   NOTIFY_ON_COMMENT_ANSWER = 0b1000

   def notify_on_mail
     self.options & NOTIFY_ON_MAIL! = 0
   end

   def notify_on_mail = (value)
     if value! = "0"
       self.options | = const
     else
       self.options & = ~ const
     end
   end
 end

So good. Now you can generalize for any number of options:

 class User <ActiveRecord :: Base
   NOTIFY_ON_MAIL = 0b0001
   NOTIFY_ON_WALL_POST = 0b0010
   NOTIFY_ON_FRIEND_REQUEST = 0b0100
   NOTIFY_ON_COMMENT_ANSWER = 0b1000

   [NOTIFY_ON_MAIL, NOTIFY_ON_WALL_POST, NOTIFY_ON_FRIEND_REQUEST, NOTIFY_ON_COMMENT_ANSWER] .each do | notifer |
     const = self.const_get (notifer)

     define_method notifer.downcase.to_sym do
       (mail_options & const)! = 0
     end

     define_method "# {notifer.downcase} =". to_sym do | flag |
       if flag! = "0"
         self.mail_options | = const
       else
         self.mail_options & = ~ const
       end
     end
   end
 end

Now to add a new option you need to change just two lines. Hmm, what if you reduce everything to one? Result:

 class User <ActiveRecord :: Base
   NOTIFY_ON_MAIL = 0b0001
   NOTIFY_ON_WALL_POST = 0b0010
   NOTIFY_ON_FRIEND_REQUEST = 0b0100
   NOTIFY_ON_COMMENT_ANSWER = 0b1000

   constants.grep (/ ^ NOTIFY _ /). each do | notifer |
     const = self.const_get (notifer)

     define_method notifer.downcase.to_sym do
       (mail_options & const)! = 0
     end

     define_method "# {notifer.downcase} =". to_sym do | flag |
       if flag! = "0"
         self.mail_options | = const
       else
         self.mail_options & = ~ const
       end
     end
   end
 end

Fields for views can also be generated in a loop.
I hope a little note brought you some thoughts :) All the best to you!

Source: https://habr.com/ru/post/95935/


All Articles