Search code examples
ruby-on-railsrubyruby-on-rails-4virtual-attribute

Virtual Attribute in Rails 4


I have a model of product and I need to write in the _form view , the number of the product that an admin wants to insert. I have another table with the Supply (number of product) so in my product table I don't have the attribute quantity , but I have just the supply_id (that links my two tables of product and supply)

Since I don't have the quantity in my product table, I used a virtual attribute on Product.

I had to change the view of the new and edit product cause in the new I want the field quantity but in the edit I don't want (cause I use another view to do this) So, I deleted the partial _form and created separate view. Also, I had to set in the controller of products that if I want to update a product, i have to call a set_quantity callback, cause I have to insert a "fake" value to fill the params[:product][:quantity]. This , because I setted the validation presence true ,on the quantity virtual field in the product model. I want to know , if all this story is right (it works , but I want a suggestion about the programming design of this story. Cause I don't like the fact that I give a fake value to fill the quantity field when I have to update a product)

Controller:

class ProductsController < ApplicationController
    include SavePicture 
    before_action :set_product, only: [:show, :edit, :update, :destroy]
    before_action :set_quantita, only: [:update]
    ....

    def set_quantita
       params[:product][:quantita]=2  #fake value for the update action
    end
    ....
end

Model:

class Product < ActiveRecord::Base
    belongs_to :supply ,dependent: :destroy
    attr_accessor :quantita
    validates :quantita, presence:true
end

Can you say me if there is a better way to fill the param[:product][:quantity] in the case of the update action? Cause i don't like the fact that i give it the value of 2. Thank you.


Solution

  • Instead of using attr_accessor you could create custom getter/setters on your product model. Note that these are not backed by an regular instance attribute.

    Also you can add a validation on the supply association instead of your virtual attribute.

    class Product < ActiveRecord::Base
      belongs_to :supply ,dependent: :destroy
      validates_associated :supply, presence:true
    
      # getter method
      def quantita
        supply
      end
    
      def quantita=(val)
        if supply
          supply.update_attributes(value: val)
        else
          supply = Supply.create(value: val)
        end
      end
    end
    

    In Ruby assignment is actually done by message passing:

    product.quantita = 1
    

    Will call product#quantita=, with 1 as the argument.

    Another alternative is to use nested attributes for the supply.

    class Product < ActiveRecord::Base
      belongs_to :supply ,dependent: :destroy
      validates_associated :supply, presence:true
      accepts_nested_attributes_for :supply
    end
    

    This means that Product accepts supply_attributes - a hash of attributes.

    class ProductsController < ApplicationController
    
      #...
      before_action :set_product, only: [:show, :edit, :update, :destroy]
    
      def create
        # will create both a Product and Supply
        @product = Product.create(product_params)
      end
    
      def update
        # will update both Product and Supply
        @product.update(product_params)
      end
    
      private
    
      def product_params
        # Remember to whitelist the nested parameters!
        params.require(:product)
              .allow(:foo, supply_attributes: [:foo, :bar])
      end
      # ...
    end