What is "cattr_accessor" (used in acts_as_audited)

Hi,

Just trying to understand what “cattr_accessor” is in the
acts_as_audited plugin. Any ideas?

[In particular I’m trying to understand how to create the appropriate
User class so I can turn on logging of userid - I’m using activeRBAC for
authentication]

Example usage:

cattr_accessor :non_audited_columns, :audited_user_class_name,
:audited_user_method

Full extract from plugin (used within here):

Copyright © 2006 Brandon K.

Permission is hereby granted, free of charge, to any person obtaining

a copy of this software and associated documentation files (the

“Software”), to deal in the Software without restriction, including

without limitation the rights to use, copy, modify, merge, publish,

distribute, sublicense, and/or sell copies of the Software, and to

permit persons to whom the Software is furnished to do so, subject to

the following conditions:

The above copyright notice and this permission notice shall be

included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,

EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF

MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND

NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE

LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION

OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION

WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

module CollectiveIdea #:nodoc:
module Acts #:nodoc:
# Specify this act if you want changes to your model to be saved in
an
# audit table. This assumes there is an audits table ready.
#
# class User < ActiveRecord::Base
# acts_as_audited
# end
#
# See
CollectiveIdea::Acts::Audited::ClassMethods#acts_as_audited
# for configuration options
module Audited
CALLBACKS = [:clear_changed_attributes, :audit_create,
:audit_update, :audit_destroy]

  def self.included(base) # :nodoc:
    base.extend ClassMethods
  end

  module ClassMethods
    # == Configuration options
    #
    # * <tt>except</tt> - Excludes fields from being saved in the 

audit log.
# By default, acts_as_audited will audit all but these fields:
#
# [self.primary_key, inheritance_column, ‘lock_version’,
‘created_at’, ‘updated_at’]
#
# You can add to those by passing one or an array of fields to
skip.
#
# class User < ActiveRecord::Base
# acts_as_audited :except => :password
# end
#
# * user_class_name - specifiy the class name of the
user class.
# This defaults to “User”. Set to false to disable user
auditing.
#
# * user_method - specify the method to call on
:user_class_name
# that returns the user that is performing the action. This
defaults to
# :current_user.
#
# == Database Schema
#
def acts_as_audited(options = {})
# don’t allow multiple calls
return if
self.included_modules.include?(CollectiveIdea::Acts::Audited::InstanceMethods)

      include CollectiveIdea::Acts::Audited::InstanceMethods

      class_eval do
        extend CollectiveIdea::Acts::Audited::SingletonMethods

        cattr_accessor :non_audited_columns, 

:audited_user_class_name, :audited_user_method

        self.non_audited_columns = [self.primary_key, 

inheritance_column, ‘lock_version’, ‘created_at’, ‘updated_at’]
self.non_audited_columns |= options[:except].is_a?(Array) ?
options[:except].collect{|column| column.to_s} :
[options[:except].to_s] if options[:except]
self.audited_user_class_name =
options[:user_class_name].nil? ? “User” : options[:user_class_name]
self.audited_user_method = options[:user_method] ||
:current_user

        has_many :audits, :as => :auditable
        after_create :audit_create
        after_update :audit_update
        before_destroy :audit_destroy
        after_save :clear_changed_attributes
      end
    end
  end

  module InstanceMethods
    # Temporarily turns off auditing while saving.
    def save_without_auditing
      without_auditing do
        save
      end
    end

    # Returns an array of attribute keys that are audited.  See 

non_audited_columns
def audited_attributes
self.attributes.keys.select { |k|
!self.class.non_audited_columns.include?(k) }
end

    # If called with no parameters, gets whether the current model 

has changed.
# If called with a single parameter, gets whether the parameter
has changed.
def changed?(attr_name = nil)
attr_name.nil? ?
(@changed_attributes && @changed_attributes.length > 0) :
(@changed_attributes &&
@changed_attributes.include?(attr_name.to_s))
end

    # Executes the block with the auditing callbacks disabled.
    #
    #   @foo.without_auditing do
    #     @foo.save
    #   end
    #
    def without_auditing(&block)
      self.class.without_auditing(&block)
    end

    private
      # Creates a new record in the audits table if applicable
      def audit_create
		logger.debug "ACTS AS: audit_create"
        write_audit(:create)
      end

      def audit_update
        write_audit(:update) if changed?
      end

      def audit_destroy
        write_audit(:destroy)
      end

      def write_audit(action = :update)
		logger.debug "ACTS AS: write_audit"
	    logger.debug "ACTS AS: self.audited_user_class_name = 

#{self.audited_user_class_name}"
#logger.debug “ACTS AS: Object.const_get(audited_user_class_name) =
#{Object.const_get(audited_user_class_name)}”
logger.debug “ACTS AS: self.audited_user_method =
#{self.audited_user_method}”
user = self.audited_user_class_name ?
Object.const_get(audited_user_class_name).send(self.audited_user_method)
: nil

        logger.debug "ACTS AS: user = #{user}"
		audits.create(:changes => @changed_attributes, :action => 

action.to_s,
:user_id => user ? user.id : nil)
end

      # clears current changed attributes.  Called after save.
      def clear_changed_attributes
        @changed_attributes = {}
      end

      # overload write_attribute to save changes to audited 

attributes
def write_attribute(attr_name, attr_value)
attr_name = attr_name.to_s
if audited_attributes.include?(attr_name)
@changed_attributes ||= {}
# get original value
old_value = @changed_attributes[attr_name] ?
@changed_attributes[attr_name].first : self[attr_name]
super(attr_name, attr_value)
new_value = self[attr_name]

          @changed_attributes[attr_name] = [old_value, new_value] if 

new_value != old_value
else
super(attr_name, attr_value)
end
end

      CALLBACKS.each do |attr_name|
        alias_method "orig_#{attr_name}".to_sym, attr_name
      end

      def empty_callback() end #:nodoc:

  end # InstanceMethods

  module SingletonMethods
    # Returns an array of columns that are audited.  See 

non_audited_columns
def audited_columns
self.columns.select { |c|
!non_audited_columns.include?(c.name) }
end

    # Executes the block with the auditing callbacks disabled.
    #
    #   Foo.without_auditing do
    #     @foo.save
    #   end
    #
    def without_auditing(&block)
      class_eval do
        CALLBACKS.each do |attr_name|
          alias_method attr_name, :empty_callback
        end
      end
      result = block.call
      class_eval do
        CALLBACKS.each do |attr_name|
          alias_method attr_name, "orig_#{attr_name}".to_sym
        end
      end
      result
    end
  end
end

end
end

ActiveRecord::Base.send :include, CollectiveIdea::Acts::Audited

==================================================

Hi Greg,

On Sep 4, 2006, at 12:34 AM, Greg H. wrote:

Just trying to understand what “cattr_accessor” is in the
acts_as_audited plugin. Any ideas?

Check out

activesupport/lib/active_support/core_ext/class/attribute_accessors.rb

You can think of cattr_accessor as an attr_accessor for a class (thus
the ‘c’), instead of for an instance of a class. Essentially, it’s a
little sugar to make life easier when giving classes read/write
attributes. (Not that such things are difficult in Ruby proper.)

Had a quick look at this (e.g. cattr_reader - extract below) - still I
little unclear to me. Do you think you could run what “cattr_accessor”
does
in layman’s terms at all? e.g. difference between class & instant
aspects. (sorry - the penny hasn’t dropped yet :slight_smile: )

=================
def cattr_reader(*syms)
syms.flatten.each do |sym|
class_eval(<<-EOS, FILE, LINE)
unless defined? @@#{sym}
@@#{sym} = nil
end

    def self.#{sym}
      @@#{sym}
    end

    def #{sym}
      @@#{sym}
    end
  EOS
end

end

thanks Ian - I think I got it - just have to go back and make sense of
this
within the code that was also doing multiple modules / includes / and
extends :slight_smile:

class Songs

@@play_count = 0

def  self.play_count
    # The cattr_reader thang
     @@play_count
end

def play
    @@play_count += 1
end

end

song1 = Songs.new
song1.play

1
song2 = Songs.new
song2.play
2
Songs.play_count
2

Hope that helps.

Hi –

On Mon, 4 Sep 2006, Greg H wrote:

Had a quick look at this (e.g. cattr_reader - extract below) - still I
little unclear to me. Do you think you could run what “cattr_accessor” does
in layman’s terms at all? e.g. difference between class & instant
aspects. (sorry - the penny hasn’t dropped yet :slight_smile: )

In layman’s terms, I’d describe it as, “A computer programming thing”
:slight_smile: But here’s a somewhat super-layman explanation.

Ruby has an attr_* family of methods:

class C
attr_accessor :x
attr_reader :y
attr_writer :z
end

These attr_* methods are meta-methods; that is, calling them actually
results in the automatic creation of other methods. In the example
above, instances of C will have the following methods:

x # reader
x= # writer
y # reader
z= # writer

The idea is to provide a convenient way to get and/or set state in an
object:

c = C.new
c.x = 1 # actually a call to the x= method!
puts c.x # a call to the x method

The way this works is that the reader and writer methods (x, x=, etc.)
store/retrieve values in/from instance variables. If you wrote them
out, the methods that the attr_* methods create for you would look
like this:

class C
def x # read “attribute” (i.e., value of instance variable)
@x
end

 def x=(value)  # set "attribute" (i.e., set instance variable)
   @x = value
 end

 def y
   @y
 end

 def z=(value)
   @z = value
 end

end

In addition to instance variables, Ruby has class variables. The
purpose of the cattr_* methods is to provide a mechanism exactly like
the above – but using class variables instead of instance variables.
Furthermore, the set/get operations are available both to instances of
the class, and to the class itself:

class D
cattr_accessor :a
end

d = D.new
d.a = 1 # set via an instance
puts D.a # get via the class

So it’s just a kind of elaboration of the original Ruby attribute
implementation. To get both the class and its instances to know about
the get and set methods, there have to be two of each. So this is
what the expanded version of the last example looks like:

class D
def self.a # class get method
@@a
end

 def self.a=(value)  # class set method
   @@a = value
 end

 def a            # instance get method
   @@a
 end

 def a=(value)    # instance set method
   @@a = value
 end

end

I personally consider “cattr” (class attribute) a misnomer.
“Attribute” suggests a property of an object. Class variables do not
map onto the state of any particular object; they are visible to a
given class, all of its subclasses, and all of the instances of itself
and those subclasses. So when you save a value in a class variable,
it’s a considerable stretch to refer to it as an “attribute”. But
anyway, the above is how it works and what it does.

David


David A. Black | [email protected]
Author of “Ruby for Rails” [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB’s Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] Ruby for Rails | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org

In ruby core programs “cattr_accessor” method is not recognized (it says
undefined method `cattr_accessor’).
I think you mean to say this method is available only in Rails, right?

example program -

class Kcattr

cattr_accessor :var
@@var = 10

end

David - thank you for taking the time to write down this explanation.
Helped me understand things. 3 years late but all the same.

Shaun

David Black wrote:

In layman’s terms, I’d describe it as, “A computer programming thing”
:slight_smile: But here’s a somewhat super-layman explanation.

Ruby has an attr_* family of methods:

class C
attr_accessor :x
attr_reader :y
attr_writer :z
end

Yes, it is only in Rails.
/activesupport/lib/active_support/core_ext/class/
attribute_accessors.rb

It is set as “nodoc” though, so the definitions don’t show up in the
API. (Any reason not to change that?)

Kevin

http://www.nullislove.com