0

What I need: I have a groups table and for -certain- groups I want user to be able to update only the name attribute. While trying to update other attributes, page must flash an error message. I'd like to realise it in model-level.

What I've done so far: I thought about making before_update callback into my Group model that asks @group.changed and when it gets back an array that contains attributes user must not be able to update then model raises an error. I combined my existing solution from here and here like this:

before_update :check_update_params

protected
  def check_update_params
    if self.only_name_update_allowed?
      changed = self.changed
      # I'd like to have somekind of empty-value check here
      # that ignores non-changed empty string and boolean-fields
      if changed.present? && changed != ["name", "updated_at"]
        raise I18n.t('my_app.groups.errors.only_name_update_available')
      end
    end
  end

Problem: Everything is ok except empty values interpreted differently in form and in db. If I log out the changed variable then I get an array that also includes some attributes that I didn't change in form but db interprets them as changed. That is due to the fact that form sends empty checkbox with a value of 0 which is saved in db as false but previous value in db was nil. Same problem is with text-fields- form form comes an empty string value like "" but database has a value of nil.

Here are some illustrating examples of my empty-value problem:

irb(main):130:0* Group.all.each do |g|
irb(main):131:1* puts "name: #{g.name} my_boolean_attr: #{g.my_boolean_attr}"
irb(main):132:1> end
  Group Load (0.7ms)  SELECT "groups".* FROM "groups"
name: group1 my_boolean_attr: 
name: group2 my_boolean_attr: 
name: group3 my_boolean_attr: 
name: group4 my_boolean_attr: 
name: group5 my_boolean_attr: 
name: group6 my_boolean_attr: false
name: group7 my_boolean_attr: false
name: group8 my_boolean_attr: false
name: group9 my_boolean_attr: false

Similar problem with text field:

irb(main):130:0* Group.all.each do |g|
irb(main):131:1* puts "name: #{g.name} my_string_attr: #{g.my_string_attr == ""}"
irb(main):132:1> end
  Group Load (0.7ms)  SELECT "groups".* FROM "groups"
name: group1 my_string_attr: false
name: group2 my_string_attr: false
name: group3 my_string_attr: false
name: group4 my_string_attr: false
name: group5 my_string_attr: false
name: group6 my_string_attr: false
name: group7 my_string_attr: false
name: group8 my_string_attr: true
name: group9 my_string_attr: true
Community
  • 1
  • 1
Andres
  • 2,099
  • 3
  • 22
  • 39

1 Answers1

0

I already managed to solve my problem with little helper method inside my model which gets called from my callback-method. I also changed the callback from before_update to before_validation to avoid a situation where user must first deal with errors from validations and, when those fixed, sees that it was pointless because he could only change name anyway. The helper-method with callback looks now like this:

before_validation :check_update_params

protected

  def check_update_params
    if self.only_name_update_allowed?
      changed = self.update_corrections(self.changed)
      if changed.present? && !(changed == ["name"] || changed == ["name", "updated_at"])
        raise I18n.t('my_app.groups.errors.only_name_update_available')
      end
    end
  end

  def update_corrections(changed)
    unneccessary = []
    changed.each do |atr|
      # Avoid pointless updates:
      if (self.changes[atr] == [nil, false] || self.changes[atr] == [nil, ""])
        unneccessary << atr #
      end
    end
    changed - unneccessary
  end

Of course another solution would have been to set default values into the db from the start but that would have assumed that for ex there is no difference between nil and false regarding to boolean values.

NB! I'm still interested if there is a better solution for this!

Andres
  • 2,099
  • 3
  • 22
  • 39