class PermissionableAttributesCodeGenerator attr_writer :attributes, :permissions, :permission_defaults def initialize(base) @base_class = base end # Saves permissions info in one column with a bitmask. def use_bitmask(permissions_field = :attributes_permissions) @permissions_field = permissions_field end # Allows you to store the permissions in a permissions table. def store_permissions_in(klass, options={}) @permission_setting_model = klass options[:as] ||= klass.to_s.tableize @permissions_association = options[:as] end # Allows the DB to store the attributes and permission defaults. def use_attributes_defined_in(klass, column_name="name", default_permission_column="default_permission_id") @attribute_fk = klass.to_s.underscore+"_id" attributes = klass.find(:all) @permission_defaults_hash = attributes.inject({}) do |defaults, a| defaults[a.send(column_name).downcase.gsub(" ","_").to_sym] = a.send(default_permission_column) defaults end @attribute_ids = attributes.inject({}) do |ids, a| ids[a.send(column_name).downcase.gsub(" ","_").to_sym] = a.id ids end @attributes = @permission_defaults_hash.keys.map(&:to_s) end # Allows the DB to store the permissions types. def use_permissions_defined_in(klass, column_name="name") @permission_type_fk = klass.to_s.underscore+"_id" @permissions_list_with_values = klass.find(:all).map{|p| [p.send(column_name), p.id]} @permissions_hash = @permissions_list_with_values.inject({}) do |hash, permission| hash[permission.first.downcase.gsub(" ","_").to_sym] = permission.last hash end @permissions = @permissions_hash.keys end #Generates the default permissions for the permissions_field in the database. Bases this off of the given permission_defaults def default_permissions_for_permissions_field default = BitAccessor.new(0, number_of_bits_per_attribute) @attributes.each_with_index { |attribute, index| default[index] = default_for(attribute) } default.to_i end def generate @base_class.send(:const_set, "ATTRIBUTES_WITH_PERMISSIONS", @attributes) @base_class.send(:const_set, "ATTRIBUTE_IDS", @attribute_ids) if @attribute_ids @base_class.send(:const_set, "ATTRIBUTES_PERMISSION_DEFAULTS", permission_defaults_hash) @base_class.send(:const_set, "ATTRIBUTE_PERMISSION_VALUES", permissions_list_with_values) @base_class.send(:const_set, "ATTRIBUTE_PERMISSIONS", permissions_hash) @base_class.send(:const_set, "ATTRIBUTE_PERMISSIONS_SYMS", permissions_hash.inject({}){|reverse, key_value| reverse[key_value.last]=key_value.first; reverse}) @base_class.send(:const_set, "ALLOWABLE_ATTRIBUTE_PERMISSION_VALUES", permissions_hash.values.sort) @base_class.class_eval <<-end_eval, __FILE__, __LINE__ + 1 class << self # Returns the default permission level for the given permissionable_attribute. def default_permission_for(permissionable_attribute) #make the default permission be 0 if no defaults are passed in self::ATTRIBUTES_PERMISSION_DEFAULTS[permissionable_attribute.to_sym] || self::ATTRIBUTES_PERMISSION_DEFAULTS[:all] || 0 end def attribute_permissions self::ATTRIBUTE_PERMISSION_VALUES end def permissions_bound_checker(permission_value) if permission_value.is_a? Symbol unless permission_index = self::ATTRIBUTE_PERMISSIONS[permission_value] error_message = "Invalid permission," + permission_value.inspect + ", passed in. The defined permissions are: " + self::ATTRIBUTE_PERMISSIONS.inspect raise(ArgumentError,error_message) end return permission_index end permission_value = permission_value.to_i return nil unless self::ALLOWABLE_ATTRIBUTE_PERMISSION_VALUES.include?(permission_value) permission_value end end end_eval if using_bitmask? @base_class.class_eval <<-end_eval, __FILE__, __LINE__ + 1 def #{@permissions_field}_bit_accessor return @#{@permissions_field}_bit_accessor ||= BitAccessor.new(self.#{@permissions_field} || 0, #{number_of_bits_per_attribute}) end def self.default_permissions return #{default_permissions_for_permissions_field} end end_eval end if using_table_permissions? @base_class.class_eval <<-end_eval, __FILE__, __LINE__ + 1 def default_attribute_setting_object_for(permission_sym, attribute_id=nil) attribute_id ||= self.class::ATTRIBUTE_IDS[permission_sym] self.#{@permissions_association}.build(:#{@attribute_fk} => attribute_id, :#{@permission_type_fk} => self.class.default_permission_for(permission_sym)) end def attribute_setting_object_for(permission_sym) attribute_id = self.class::ATTRIBUTE_IDS[permission_sym] self.#{@permissions_association}.detect { |permission_setting| permission_setting.#{@attribute_fk} == attribute_id } || default_attribute_setting_object_for(permission_sym, attribute_id) end end_eval end @attributes.each_with_index do |permissionable_attribute, attribute_index| if using_bitmask? @base_class.class_eval <<-end_eval, __FILE__, __LINE__ + 1 #Define the setters for the permissions. def #{permissionable_attribute}_permissions=(permission_value) default = self.class.default_permission_for(:#{permissionable_attribute}) self.#{@permissions_field}_bit_accessor[#{attribute_index}] = self.class.permissions_bound_checker(permission_value) || default self.#{@permissions_field} = self.#{@permissions_field}_bit_accessor.to_i end #Also need to define getter since those won't be provided by the class def #{permissionable_attribute}_permissions self.#{@permissions_field}_bit_accessor[#{attribute_index}] end end_eval elsif using_table_permissions? @base_class.class_eval <<-end_eval, __FILE__, __LINE__ + 1 #Define the setters for the permissions. Proxy to the assication objects. def #{permissionable_attribute}_permissions=(permission_value) default = self.class.default_permission_for(:#{permissionable_attribute}) new_permission = self.class.permissions_bound_checker(permission_value) || default attribute_setting = attribute_setting_object_for(:#{permissionable_attribute}) attribute_setting.#{@permission_type_fk} = new_permission #Always saving to keep behaviour consistent... attribute_setting.save! #unless attribute_setting.new_record? # New records will be saved when object is saved. end #Also need to define getter to delegate to the object in the association proxy def #{permissionable_attribute}_permissions attribute_setting_object_for(:#{permissionable_attribute}).#{@permission_type_fk} end end_eval else @base_class.class_eval <<-end_eval, __FILE__, __LINE__ + 1 #Define the setters for the permissions. def #{permissionable_attribute}_permissions=(permission_value) default = self.class.default_permission_for(:#{permissionable_attribute}) super( self.class.permissions_bound_checker(permission_value) || default) end end_eval end #Now define the predicate helpers for the permissions of each attribute. @permissions.each do |permission| @base_class.class_eval <<-end_eval, __FILE__, __LINE__ + 1 def #{permissionable_attribute}_#{permission}? self.#{permissionable_attribute}_permissions == self.class::ATTRIBUTE_PERMISSIONS[:#{permission}] end def #{permissionable_attribute}_permissions_as_sym ; self.class::ATTRIBUTE_PERMISSIONS_SYMS[#{permissionable_attribute}_permissions] end end_eval end end end protected def max_permissions_value @permissions.size - 1 end def number_of_bits_per_attribute @number_of_bits_per_attribute ||= max_permissions_value.to_s(2).size end def permissions_list_with_values return @permissions_list_with_values if @permissions_list_with_values value = -1 @permissions_list_with_values = @permissions.inject([]){|m, permission| value+=1; m << [permission.to_s.humanize, value]} end def permission_defaults_hash return @permission_defaults_hash if @permission_defaults_hash @permission_defaults.each do |key, default| default = @permissions.rindex(default) if default.is_a? Symbol raise "Incorrect permission value for #{key.inspect}." if default.nil? || default < 0 || default > max_permissions_value @permission_defaults[key] = default end @permission_defaults_hash = @permission_defaults end def permissions_hash return @permissions_hash if @permissions_hash index = -1 @permissions_hash ||= @permissions.inject({}){|hash, key| hash[key] = index+=1; hash} end def using_table_permissions? !!@permission_setting_model end def using_bitmask? !!@permissions_field end def default_for(attribute) permission_defaults_hash[attribute.to_sym] || permission_defaults_hash[:all] || 0 end end