Search code examples
ruby-on-railsvirtual-attribute

Why are my virtual attributes not working?


I'm using Rails 3.2.17 trying to access two virtual attributes (expiration_amount & expiration_format) in a model but nothing I've tried allows me to access them.

Everything else seems to update correctly, however in my logs the virtual attributes always come up blank.

EDIT I added both :expiration_amount :expiration_format to attr_accessible as suggested but it's still not working. They both come up blank. One thing I did notice was that the logger calls were happening after it hits the DB. Is before_update not the correct filter for this?

EDIT 2 Is it possible Devise is preventing the virtual attributes from being accessed?

EDIT 3 I just decided to kick it off from the controller (which is working), though I'm still curious as to why I couldn't access those virtual attributes in the model.

Working Controller action (Update method)

@user.set_expiration(params[:expiration_amount].to_i, params[:expiration_format])

Working Model method

def set_expiration(amount, format)
  if defined?(amount) && defined?(format)
    case format
    when "hours" 
      self.expiration = amount.hours.from_now
    when "days"
      self.expiration = amount.days.from_now
    when "weeks"
      self.expiration = amount.weeks.from_now
    else
      self.expiration = amount.hours.from_now
    end
  end
end

---ORIGINAL CODE---

User.rb

class User < ActiveRecord::Base
  attr_accessible               :email, 
                                :password, 
                                :password_confirmation, 
                                :remember_me,
                                :first_name, 
                                :last_name, 
                                :company, 
                                :phone, 
                                :expiration,
                                :expiration_amount, 
                                :expiration_format

  attr_accessor                 :expiration_amount, 
                                :expiration_format

  before_update                 :set_expiration

  def set_expiration
    logger.debug "DEBUG - expiration_amount: #{expiration_amount}"
    logger.debug "DEBUG - expiration_format: #{expiration_format}"

    if expiration_amount && expiration_format
      case expiration_format
      when "hours" 
        self.expiration = expiration_amount.hours.from_now
      when "days"
        self.expiration = expiration_amount.days.from_now
      when "weeks"
        self.expiration = expiration_amount.weeks.from_now
      else
        self.expiration = expiration_amount.hours.from_now
      end
    end
  end
end

form

= form_for(resource, :as => resource_name, :url => admins_user_update_path(resource), :html => { :method => :put }) do |f| 
  = devise_error_messages! 

  %fieldset{id: "edit-user-account"}
    %legend
      Set User Access

    .field.required
      %label
        "Allow access for "
      = select_tag "expiration_amount", options_for_select(expiration_amount, 3)
      = select_tag "expiration_format", options_for_select(expiration_format, "days")
      %span.instructions
        From Now

    .field
      = f.submit "Grant Access"

logs

  Parameters: {"utf8"=>"✓", "authenticity_token"=>"xxx=", "expiration_amount"=>"3", "expiration_format"=>"days", "commit"=>"Grant Access", "id"=>"5"}
User Load (0.6ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", "5"]]
Admin Load (0.4ms)  SELECT "admins".* FROM "admins" WHERE "admins"."id" = 1 LIMIT 1
 (0.3ms)  BEGIN
DEBUG - expiration_amount: 
DEBUG - expiration_format: 
(0.3ms)  COMMIT
Redirected to http://localhost:8080/admins/users

Solution

  • I finally figured out what the problem was! Because I was using select_tags with stings it wasn't naming them correctly, so:

    = select_tag "expiration_amount", options_for_select(expiration_amount, 3)
    = select_tag "expiration_format", options_for_select(expiration_format, "days")
    

    was rendering as:

    <select id="expiration_amount" name="expiration_amount">
    <select id="expiration_format" name="expiration_format">
    

    But what they needed to be for the model to pick them up is:

    = f.select :expiration_amount, options_for_select(expiration_amount, 3)
    = f.select :expiration_format, options_for_select(expiration_format, "days")
    
    <select id="user_expiration_amount" name="user[expiration_amount]">
    <select id="user_expiration_format" name="user[expiration_format]">
    

    After that I was able to do everything in the model as opposed to the controller.

    model method

    before_update :set_expiration
    
    def set_expiration
      logger.debug "DEBUG @ User - expiration_amount: #{expiration_amount}"
      logger.debug "DEBUG @ User - expiration_format: #{expiration_format}"
    
      if defined?(expiration_amount) && defined?(expiration_format)
        amount = expiration_amount.to_i
    
        case expiration_format
        when "hours" 
          self.expiration = amount.hours.from_now
        when "days"
          self.expiration = amount.days.from_now
        when "weeks"
          self.expiration = amount.weeks.from_now
        else
          self.expiration = 0.hours.from_now
        end
      end
    end