Birds-Eye.Net
All things broadband and more...
 
Web Birds-Eye.Net

What's New?

Ruby on Rails (RoR)
Programming Reference


Models
External database connections
Passing current_user into model
Passing object into model
Using static lookup tables
Validates IF
Validates MongoMapper

Views
Dynamically delete form element
Edit create nested data
HTML form field check_box
Layout jQuery datatable module
Select array
Select cascading via JS
Text_area Array
Text_area listing submit
Text field format time

Controllers
Dynamic model selection
Including first item from a sorted desc table
Using from_unixtime on epoch dates
Custom SQL Query Examples

Rack
Integrated NTLM/Kerberos Authentication
Pass-through Authentication w/ NTLM

ActionMailer
Broken links in emails

Rails General
Add, Subtract, Multiply, and Divide
Calculate number of weekdays for date range
Date->Epoch & Epoch->Date
Calculate past/present payroll dates
Extract first letter of each word
Hash of hashes assignment
Using: variable as hash index

jQuery
jQuery accordion MongoDB

Rails Framework Examples

Apotomo Widget Using Erb

MySQL
Converting Julian Dates to Epoch

d3 Charting
Configuration to Work with Rails Apps
A simple bar chart example

Other
Setup VPN on iMac
SSH Key Generation

More to come

 

Passing Objects into Model using Virtual Attributes

By: Bruce Bahlmann - Contributing Author (your feedback is important to us!)

In rails development, you may encounter a need to pull in other information into your model which you need to more fully process the data before saving. An example of this is common when you call upon a model to log changes to a particular set of data. In this case, you would want to be able to compare previously stored values with those being submitted by the user and possibly saving to the database. There are some suggestions to use before_save or after_validation filters to make this happen. However, regardless of how you implement this, how do get the object (the previously stored version of the record being updated) into the model to allow you to run a comparison?

Fortunately, Ruby on Rails (RoR) provides an easy way to do this that you don't have to worry about problems with simultaneous interactive sessions or what you are using for an application server or web server. What I believe is the easiest way to pass additional information into models is to use virtual attributes. Here is how it works:

[app/models/inventory.rb]
class Inventory < ActiveRecord::Base
 attr_accessor :cUser
 attr_accessor :pInventory
...
First you define a virtual attribute within your model. Note, you do not need to create a method for it, just simply define it as above. In this example I have defined two virtual attributes that will be required to carry out proper application logging - the pInventory (representing the previously stored inventory) and the cUser (representing the currently logged in user - the person making the changes).

Keep in mind, that virtual attributes are just that - virtual. While you may attach or associate them with objects whose contents may be committed to a database, the virtual attributes will NOT be committed. Virtual attributes do not pertain to columns in a database.

[app/controllers/inventory.rb]
...

 # POST /inventories
 # POST /inventories.json
 def create 
   @inventory = Inventory.new(params[:inventory])

   ## Send model virtual attributes (these are not committed to the database - simply used to log activity
   @inventory.cUser = @current_user.name
   @inventory.pInventory = @inventory.to_json 
...

 # PUT /inventories/1
 # PUT /inventories/1.json
 def update
   @inventory = Inventory.find(params[:id])

   ## Send model virtual attributes (these are not committed to the database - simply used to log activity
   params[:inventory]["cUser"] = @current_user.name
   params[:inventory]["pInventory"] = @inventory.to_json 
...

Now that the virtual attributes are defined, we need to give each attribute a value some place where the value is available. There are two areas within the controller that we want to populate pInventory and cUser - create and update. Both actions involve processing form information submitted by the user. The objective here is to assign pInventory and cUser values just prior to their being handed to a model where verification and saving happen - this prevents simultaneous interactive sessions from overwriting this value.

In the particular case of pInventory, it is not a simple matter of passing a string. Rather, pInventory represents an object so to pass this to the model we apply one simple piece of magic (serialize the object using JSON) so that it can be sent on to the model embedded similarly with other fields.

[app/models/inventory.rb]
class Inventory < ActiveRecord::Base
 attr_accessor :cUser
...

 validate :custom_validation

 def custom_validation
 
  if errors.count > 0
    ## Any post processing of errors prior to returning back to edit/new page

  else 
    ## Proceed with any finished processing prior to saving
    self.purchaseDate = Time.parse(purchaseDate.to_s).to_i
    pi = ActiveSupport::JSON.decode(pInventory)
    modified = 1

    ## Process notes
    time = Time.now.to_i

    if pi["nKeyID"].to_s == ""
      ## Unique to NEW (create) Inventory
      if notes == ''
        self.notes = "#{time}|#{cUser}|Inventory first entered"
      else 
        self.notes = "#{time}|#{cUser}|#{notes}|_|#{time}|#{cUser}|Inventory first entered"
      end
    else 
      ## Compare previous/current values for changes (update in notes) - Unique to updating Inventory
      comparisonText = ''
      pi.each do |k,v|
        next if k == 'notes'
        next if k == 'lmodified'
        comparisonText += ", #{k} changed from [#{pi[k]}] to [#{self.send(k)}]" if pi[k].to_s != self.send(k).to_s
      end
      comparisonText.gsub!(/^\,\s/,'')

      ## Build notes based on changes detected
      if notes == ''
        if comparisonText == ''
          ## No change - record untouched
          self.lmodified = pi["lmodified"]
          self.notes = pi["notes"]
          modified = 0
        else 
          self.notes = "#{time}|#{cUser}|#{comparisonText}|_|#{pi["notes"]}"
        end
      else 
        if comparisonText == ''
          self.notes = "#{time}|#{cUser}|#{notes}|_|#{pi["notes"]}"
        else 
          self.notes = "#{time}|#{cUser}|#{notes}|_|#{time}|#{cUser}|#{comparisonText}|_|#{pi["notes"]}"
        end
      end
    end
    self.lmodified = time if modified == 1 
...

Finally, back in the model, we can now work with the values we assigned pInventory and cUser in the controller with confidence that their values will not be overwritten. Note that I used decode to unserialize the passed JSON structure - this allows full use of the passed object (albeit now it is a hash). We also must use the send method to access elements within the object when we are looping through the object's attributes to compare previous and current for any change that we want to note in the log relative the user who made that change.

It is worth mentioning, that one can approach logging different ways. Rails seems to more naturally prefer logging that is separate from the data it is referencing (in other words a separate table that uniquely provides logging). Perhaps I am a bit old school, but I much prefer logging that is incorporated within the record it represents. If you have this incorporated logging, you need to do something like the above as rails scaffolding isn't going to build this for you.  

Can Birds-Eye.Net help you or your Company?
Receive your Birds-Eye.Net articles and white papers hot off the presses by adding our RSS feed to your reader.

 

(C) Copyright Birds-Eye.Net, All rights reserved.
It is against the law to reproduce this content or any portion of it in any form without the explicit written permission of Birds-Eye Network Services, LLC. Federal copyright law (17 USC 504) makes it illegal, punishable with fines up to $100,000 per violation plus attorney's fees.