# File lib/big_record/model.rb, line 784 def primary_key raise NotImplemented end
class BigRecord::Model
Constants
- ID_FIELD_SEPARATOR
Constants for special characters in generated IDs. An ID might then look like this: 'United_States-Hawaii-Oahu-Honolulu-b9cef848-a4e0-11dc-a7ba-0018f3137ea8'
- ID_WHITE_SPACE_CHAR
Public Class Methods
Overwrite the default class equality method to provide support for association proxies.
# File lib/big_record/model.rb, line 822 def ===(object) object.is_a?(self) end
Define aliases to the fully qualified attributes
# File lib/big_record/model.rb, line 910 def alias_attribute(alias_name, fully_qualified_name) self.class_eval <<-EOF def #{alias_name} read_attribute("#{fully_qualified_name}") end def #{alias_name}=(value) write_attribute("#{fully_qualified_name}", value) end EOF end
Specifies a white list of model attributes that can be set via
mass-assignment, such as new(attributes)
,
update_attributes(attributes)
, or
attributes=(attributes)
This is the opposite of the attr_protected
macro:
Mass-assignment will only set attributes in this list, to assign to the
rest of attributes you can use direct writer methods. This is meant to
protect sensitive attributes from being overwritten by malicious users
tampering with URLs or forms. If you'd rather start from an all-open
default and restrict attributes as needed, have a look at
attr_protected
.
class Customer < ActiveRecord::Base attr_accessible :name, :nickname end customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent") customer.credit_rating # => nil customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" } customer.credit_rating # => nil customer.credit_rating = "Average" customer.credit_rating # => "Average"
# File lib/big_record/model.rb, line 1004 def attr_accessible(*attributes) write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || [])) end
Attributes listed as create_accessible work with mass assignment ONLY on creation. After that, any updates to that attribute will be protected from mass assignment. This differs from ::attr_readonly since that macro prevents attributes from ever being changed (even with the explicit setters) after the record is created.
class Customer < BigRecord::Base attr_create_accessible :name end customer = Customer.new(:name => "Greg") customer.name # => "Greg" customer.save # => true customer.attributes = { :name => "Nerd" } customer.name # => "Greg" customer.name = "Nerd" customer.name # => "Nerd"
# File lib/big_record/model.rb, line 1040 def attr_create_accessible(*attributes) write_inheritable_attribute(:attr_create_accessible, Set.new(attributes.map(&:to_s)) + (create_accessible_attributes || [])) end
Attributes named in this macro are protected from mass-assignment, such as
new(attributes)
, update_attributes(attributes)
,
or attributes=(attributes)
.
Mass-assignment to these attributes will simply be ignored, to assign to them you can use direct writer methods. This is meant to protect sensitive attributes from being overwritten by malicious users tampering with URLs or forms.
class Customer < ActiveRecord::Base attr_protected :credit_rating end customer = Customer.new("name" => David, "credit_rating" => "Excellent") customer.credit_rating # => nil customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" } customer.credit_rating # => nil customer.credit_rating = "Average" customer.credit_rating # => "Average"
To start from an all-closed default and enable attributes as needed, have a
look at attr_accessible
.
# File lib/big_record/model.rb, line 971 def attr_protected(*attributes) write_inheritable_attribute(:attr_protected, Set.new(attributes.map {|a| a.to_s}) + (protected_attributes || [])) end
Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
# File lib/big_record/model.rb, line 1014 def attr_readonly(*attributes) write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || [])) end
# File lib/big_record/model.rb, line 826 def base_class raise NotImplemented end
Log and benchmark multiple statements in a single block.
@example
Project.benchmark("Creating project") do project = Project.create("name" => "stuff") project.create_manager("name" => "David") project.milestones << Milestone.find(:all) end
The benchmark is only recorded if the current level of the logger matches
the log_level
, which makes it easy to include benchmarking
statements in production software that will remain inexpensive because the
benchmark will only be conducted if the log level is low enough.
The logging of the multiple statements is turned off unless
use_silence
is set to false.
# File lib/big_record/model.rb, line 802 def benchmark(title, log_level = Logger::DEBUG, use_silence = true) if logger && logger.level == log_level result = nil seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield } logger.add(log_level, "#{title} (#{'%.5f' % seconds})") result else yield end end
Macro for defining a new column for a model. Invokes {create_column} and adds the new column into the model's column hash.
@param type [Symbol, String] Column type as defined in the source of {ConnectionAdapters::Column#klass} @option options [true,false] :collection Whether this column is a collection. @option options [String] :alias Define an alias for the column that cannot be inferred. By default, 'attribute:name' will be aliased to 'name'. @option options [String] :default Default value to set for this column.
@return [ConnectionAdapters::Column] The column object created.
# File lib/big_record/model.rb, line 892 def column(name, type, options={}) name = name.to_s name = "#{self.default_column_prefix}#{name}" unless (name =~ /:/) || self.default_column_prefix.blank? @columns_hash = default_columns unless @columns_hash # The other variables that are cached and depend on @columns_hash need to be reloaded invalidate_columns c = create_column(name, type, options) @columns_hash[c.name] = c alias_attribute c.alias, c.name if c.alias c end
Returns an array of column names as strings.
# File lib/big_record/model.rb, line 859 def column_names @column_names = columns_hash.keys end
Override this method in the subclasses to add new columns. This is different from ActiveRecord because the number of columns in an Hbase table is variable.
# File lib/big_record/model.rb, line 832 def columns @columns = columns_hash.values end
Returns a hash of column objects for the table associated with this class.
# File lib/big_record/model.rb, line 837 def columns_hash unless @all_columns_hash # add default hbase columns @all_columns_hash = if self == base_class if @columns_hash default_columns.merge(@columns_hash) else @columns_hash = default_columns end else if @columns_hash superclass.columns_hash.merge(@columns_hash) else superclass.columns_hash end end end @all_columns_hash end
Returns an array of column objects where the primary id, all columns ending in “_id” or “_count”, and columns used for single table inheritance have been removed.
# File lib/big_record/model.rb, line 865 def content_columns @content_columns ||= columns.reject{|c| c.primary || "id"} end
Returns an array of all the attributes that have been specified as create_accessible.
# File lib/big_record/model.rb, line 1045 def create_accessible_attributes read_inheritable_attribute(:attr_create_accessible) end
Default column prefix used when auto-generating column attribute names
# File lib/big_record/model.rb, line 1050 def default_column_prefix "" end
Finder methods must instantiate through this method.
# File lib/big_record/model.rb, line 938 def instantiate(raw_record) record = self.allocate record.deserialize(raw_record) record.preinitialize(raw_record) record.instance_variable_set(:@new_record, false) record.send("safe_attributes=", raw_record, false) record end
New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved (pass a hash with key names matching the associated table column names). In both instances, valid attribute keys are determined by the column names of the associated table – hence you can't have attributes that aren't part of the table columns.
@param [Hash] Optional hash argument consisting of keys that match the names of the columns, and their values.
# File lib/big_record/model.rb, line 144 def initialize(attrs = nil) preinitialize(attrs) @attributes = attributes_from_column_definition self.attributes = attrs unless attrs.nil? end
Evaluate the name of the column of the primary key only once
Returns an array of all the attributes that have been specified as readonly.
# File lib/big_record/model.rb, line 1019 def readonly_attributes read_inheritable_attribute(:attr_readonly) end
Silences the logger for the duration of the block.
# File lib/big_record/model.rb, line 814 def silence old_logger_level, logger.level = logger.level, Logger::ERROR if logger yield ensure logger.level = old_logger_level if logger end
# File lib/big_record/model.rb, line 91 def self.store_primary_key? false end
Guesses the table name, but does not decorate it with prefix and suffix information.
# File lib/big_record/model.rb, line 1092 def undecorated_table_name(class_name = base_class.name) table_name = Inflector.underscore(Inflector.demodulize(class_name)) table_name = Inflector.pluralize(table_name) if pluralize_table_names table_name end
Protected Class Methods
Returns the class type of the record using the current module as a prefix. So descendents of MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
# File lib/big_record/model.rb, line 1086 def compute_type(type_name) type_name.constantize end
Creates a {ConnectionAdapters::Column} object.
# File lib/big_record/model.rb, line 1067 def create_column(name, type, options) ConnectionAdapters::Column.new(name, type, options) end
# File lib/big_record/model.rb, line 1071 def default_columns {} end
# File lib/big_record/model.rb, line 1075 def default_views {:all=>ConnectionAdapters::View.new('all', nil, self), :default=>ConnectionAdapters::View.new('default', nil, self)} end
# File lib/big_record/model.rb, line 1060 def invalidate_columns @columns = nil @column_names = nil @content_columns = nil end
# File lib/big_record/model.rb, line 1055 def invalidate_views @views = nil @view_names = nil end
Public Instance Methods
Returns true if the comparison_object
is the same object, or
is of the same type and has the same id.
# File lib/big_record/model.rb, line 310 def ==(comparison_object) comparison_object.equal?(self) || (comparison_object.instance_of?(self.class) && comparison_object.id == id && !comparison_object.new_record?) end
Returns the value of the attribute identified by attr_name
after it has been typecast (for example, “2004-12-12” in a data column is
cast to a date object, like Date.new(2004, 12, 12)). (Alias for the
protected #read_attribute
method).
# File lib/big_record/model.rb, line 225 def [](attr_name) read_attribute(attr_name) end
Updates the attribute identified by attr_name
with the
specified value
. (Alias for the protected #write_attribute method).
# File lib/big_record/model.rb, line 231 def []=(attr_name, value) write_attribute(attr_name, value) end
# File lib/big_record/model.rb, line 255 def all_attributes_loaded=(loaded) @all_attributes_loaded = loaded end
# File lib/big_record/model.rb, line 259 def all_attributes_loaded? @all_attributes_loaded end
Format attributes nicely for inspect.
# File lib/big_record/model.rb, line 264 def attribute_for_inspect(attr_name) value = read_attribute(attr_name) if value.is_a?(String) && value.length > 50 "#{value[0..50]}...".inspect elsif value.is_a?(Date) || value.is_a?(Time) %Q("#{value.to_s(:db)}") else value.inspect end end
Returns an array of names for the attributes available on this object sorted alphabetically.
# File lib/big_record/model.rb, line 295 def attribute_names @attributes.keys.sort end
Returns true if the specified attribute
has been set by the
user or by a database load and is neither nil nor empty? (the latter only
applies to objects that respond to empty?, most notably Strings).
@return [String, Symbol] Name of an attribute. @return [true,false] Whether that attribute exists.
# File lib/big_record/model.rb, line 281 def attribute_present?(attribute) value = read_attribute(attribute) !value.blank? or value == 0 end
Get the attributes hash of the object.
@return [Hash] a duplicated attributes hash
# File lib/big_record/model.rb, line 207 def attributes() @attributes.dup end
Allows you to set all the attributes at once by passing in a hash with keys
matching the attribute names (which again matches the column names).
Sensitive attributes can be protected from this form of mass-assignment by
using the attr_protected
macro. Or you can alternatively
specify which attributes can be accessed in with the
attr_accessible
macro. Then all the attributes not included in
that won't be allowed to be mass-assigned.
# File lib/big_record/model.rb, line 240 def attributes=(new_attributes, guard_protected_attributes = true) return if new_attributes.nil? attributes = new_attributes.dup attributes.stringify_keys! multi_parameter_attributes = [] attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes attributes.each do |k, v| k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v) end assign_multiparameter_attributes(multi_parameter_attributes) end
Overridden by FamilySpanColumns Returns the column object for the named attribute.
# File lib/big_record/model.rb, line 305 def column_for_attribute(name) self.class.columns_hash[name.to_s] end
Returns the connection adapter of the current session.
# File lib/big_record/model.rb, line 413 def connection self.class.connection end
# File lib/big_record/model.rb, line 157 def deserialize(attrs = nil) end
Deletes the record in the database and freezes this instance to reflect that no changes should be made (since they can't be persisted).
# File lib/big_record/model.rb, line 390 def destroy raise NotImplemented end
Delegates to ==
# File lib/big_record/model.rb, line 318 def eql?(comparison_object) self == (comparison_object) end
Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
# File lib/big_record/model.rb, line 350 def freeze @attributes.freeze; self end
Checks whether the object has had its attributes hash frozen.
@return [true,false]
# File lib/big_record/model.rb, line 357 def frozen? @attributes.frozen? end
Returns true if the given attribute is in the attributes hash
@return [String, Symbol] Name of an attribute. @return [true,false] Whether that attribute exists in the attributes hash.
# File lib/big_record/model.rb, line 290 def has_attribute?(attr_name) @attributes.has_key?(attr_name.to_s) end
Delegates to id in order to allow two records of the same type and id to work with something like:
[ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
# File lib/big_record/model.rb, line 324 def hash id.hash end
# File lib/big_record/model.rb, line 299 def human_attribute_name(attribute_key) self.class.human_attribute_name(attribute_key) end
A model instance's primary key is always available as model.id whether you name it the default 'id' or set it to something else.
# File lib/big_record/model.rb, line 190 def id attr_name = self.class.primary_key c = column_for_attribute(attr_name) define_read_method(:id, attr_name, c) if self.class.generate_read_methods read_attribute(attr_name) end
Sets the primary ID.
# File lib/big_record/model.rb, line 366 def id=(value) write_attribute(self.class.primary_key, value) end
Returns the contents of the record as a nicely formatted string.
# File lib/big_record/model.rb, line 428 def inspect attributes_as_nice_string = self.class.column_names.collect { |name| if has_attribute?(name) || new_record? "#{name}: #{attribute_for_inspect(name)}" end }.compact.join(", ") "#<#{self.class} #{attributes_as_nice_string}>" end
Returns true if this object hasn't been saved yet – that is, a record for the object doesn't yet exist in the data store.
# File lib/big_record/model.rb, line 372 def new_record? false end
Callback method meant to be overriden by subclasses if they need to preload some attributes before initializing the record. (usefull when using a meta model where the list of columns depends on the value of an attribute)
# File lib/big_record/model.rb, line 153 def preinitialize(attrs = nil) @attributes = {} end
Records loaded through joins with piggy-back attributes will be marked as read only as they cannot be saved and return true to this query.
# File lib/big_record/model.rb, line 418 def readonly? @readonly == true end
Reloads the attributes of this object from the database. The optional options argument is passed to find when reloading so you may do e.g. record.reload(:lock => true) to reload the same record with an exclusive row lock.
@return Itself with reloaded attributes.
# File lib/big_record/model.rb, line 217 def reload(options = nil) @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes')) self end
A Person object with a name attribute can ask person.respond_to?(“name”), person.respond_to?(“name=”), and person.respond_to?(“name?”) which will all return true.
# File lib/big_record/model.rb, line 333 def respond_to?(method, include_priv = false) if @attributes.nil? return super elsif attr_name = self.class.column_methods_hash[method.to_sym] return true if @attributes.include?(attr_name) || attr_name == self.class.primary_key return false if self.class.read_methods.include?(attr_name) elsif @attributes.include?(method.to_s) return true elsif md = self.class.match_attribute_method?(method.to_s) return true if @attributes.include?(md.pre_match) end # super must be called at the end of the method, because the inherited respond_to? # would return true for generated readers, even if the attribute wasn't present super end
For checking respond_to? without searching the attributes (which is faster).
Safe version of attributes= so that objects can be instantiated even if columns are removed.
@param [Hash] Attribute hash consisting of the column name and their values. @param [true, false] Pass the attributes hash through {#remove_attributes_protected_from_mass_assignment}
# File lib/big_record/model.rb, line 165 def safe_attributes=(new_attributes, guard_protected_attributes = true) return if new_attributes.nil? attributes = new_attributes.dup attributes.stringify_keys! multi_parameter_attributes = [] attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes attributes.each do |k, v| begin k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v) if v rescue logger.debug "#{__FILE__}:#{__LINE__} Warning! Ignoring attribute '#{k}' because it doesn't exist anymore" end end begin assign_multiparameter_attributes(multi_parameter_attributes) rescue logger.debug "#{__FILE__}:#{__LINE__} Warning! Ignoring multiparameter attributes because some don't exist anymore" end end
-
No record exists: Creates a new record with values matching those of the object attributes.
-
A record does exist: Updates the record with values matching those of the object attributes.
# File lib/big_record/model.rb, line 378 def save raise NotImplemented end
Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a RecordNotSaved exception
# File lib/big_record/model.rb, line 384 def save! raise NotImplemented end
Default #to_s method that just returns invokes the {#id} method
@return [String] The row identifier/id of the record
# File lib/big_record/model.rb, line 200 def to_s id end
Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records. Note: This method is overwritten by the Validation module that'll make sure that updates made with this method doesn't get subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
# File lib/big_record/model.rb, line 397 def update_attribute(name, value) raise NotImplemented end
Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will fail and false will be returned.
# File lib/big_record/model.rb, line 403 def update_attributes(attributes) raise NotImplemented end
Updates an object just like BigRecord::Base#update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
# File lib/big_record/model.rb, line 408 def update_attributes!(attributes) raise NotImplemented end
Protected Instance Methods
Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done by calling new on the column type or aggregation type (through composed_of) object with these parameters. So having the pairs written_on(1) = “2004”, written_on(2) = “6”, written_on(3) = “24”, will instantiate written_on (a date type) with Date.new(“2004”, “6”, “24”). You can also specify a typecast character in the parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.
# File lib/big_record/model.rb, line 519 def assign_multiparameter_attributes(pairs) execute_callstack_for_multiparameter_attributes( extract_callstack_for_multiparameter_attributes(pairs) ) end
Handle *= for method_missing.
# File lib/big_record/model.rb, line 1106 def attribute=(attribute_name, value) write_attribute(attribute_name, value) end
Handle *? for method_missing.
# File lib/big_record/model.rb, line 1101 def attribute?(attribute_name) query_attribute(attribute_name) end
Handle *_before_type_cast for method_missing.
# File lib/big_record/model.rb, line 1111 def attribute_before_type_cast(attribute_name) read_attribute_before_type_cast(attribute_name) end
Initializes the attributes array with keys matching the columns from the linked table and the values matching the corresponding default value of that column, so that a new instance, or one populated from a passed-in Hash, still has all the attributes that instances loaded from the database would.
# File lib/big_record/model.rb, line 504 def attributes_from_column_definition self.class.columns.inject({}) do |attributes, column| unless column.name == self.class.primary_key attributes[column.name] = column.default end attributes end end
The primary key and inheritance column can never be set by mass-assignment for security reasons.
# File lib/big_record/model.rb, line 677 def attributes_protected_by_default # default = [ self.class.primary_key, self.class.inheritance_column ] # default << 'id' unless self.class.primary_key.eql? 'id' # default [] end
# File lib/big_record/model.rb, line 438 def clone_in_persistence_format validate_attributes_schema data = {} # normalized attributes without the id @attributes.keys.each do |key| next if !self.class.store_primary_key? and (key == self.class.primary_key) value = read_attribute(key) if value.kind_of?(Embedded) data[key] = value.clone_in_persistence_format elsif value.is_a?(Array) data[key] = value.collect do |e| if e.kind_of?(Embedded) e.clone_in_persistence_format else e end end else data[key] = value end end data end
Creates a record with values matching those of the instance attributes and returns its id. Generate a UUID as the row key.
# File lib/big_record/model.rb, line 588 def create raise NotImplemented end
# File lib/big_record/model.rb, line 582 def create_or_update raise NotImplemented end
Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself.
# File lib/big_record/model.rb, line 526 def execute_callstack_for_multiparameter_attributes(callstack) errors = [] callstack.each do |name, values| # TODO: handle aggregation reflections # klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass column = column_for_attribute(name) if column klass = column.klass # Ugly fix for time selectors so that when any value is invalid the value is considered invalid, hence nil if values.empty? or (column.type == :time and !values[-2..-1].all?) or ([:date, :datetime].include?(column.type) and !values.all?) send(name + "=", nil) else # End of the ugly time fix... values = [2000, 1, 1, values[-2], values[-1]] if column.type == :time and !values[0..2].all? begin send(name + "=", Time == klass ? (@@default_timezone == :utc ? klass.utc(*values) : klass.local(*values)) : klass.new(*values)) rescue => ex errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name) end end end end unless errors.empty? raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes" end end
# File lib/big_record/model.rb, line 554 def extract_callstack_for_multiparameter_attributes(pairs) attributes = { } for pair in pairs multiparameter_name, value = pair attribute_name = multiparameter_name.split("(").first attributes[attribute_name] = [] unless attributes.include?(attribute_name) position = find_parameter_position(multiparameter_name) value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) attributes[attribute_name] << [position, value] end attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } } end
# File lib/big_record/model.rb, line 573 def find_parameter_position(multiparameter_name) multiparameter_name.scan(/\(([0-9]*).*\)/).first.first end
Generate a new id with the UUIDTools library. Override this method to use another id generator.
# File lib/big_record/model.rb, line 496 def generate_new_id UUIDTools::UUID.random_create.to_s end
# File lib/big_record/model.rb, line 672 def log_protected_attribute_removal(*attributes) logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}" end
Allows access to the object attributes, which are held in the @attributes hash, as were they first-class methods. So a Person class with a name attribute can use Person#name and Person#name= and never directly use the attributes hash – except for multiple assigns with ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that the completed attribute is not nil or 0.
It's also possible to instantiate related objects, so a Client class belonging to the clients table with a master_id foreign key can instantiate master through Client#master.
# File lib/big_record/model.rb, line 613 def method_missing(method_id, *args, &block) method_name = method_id.to_s if column_for_attribute(method_name) or ((md = /\?$/.match(method_name)) and column_for_attribute(query_method_name = md.pre_match) and method_name = query_method_name) define_read_methods if self.class.read_methods.empty? && self.class.generate_read_methods md ? query_attribute(method_name) : read_attribute(method_name) elsif self.class.primary_key.to_s == method_name id elsif (md = self.class.match_attribute_method?(method_name)) attribute_name, method_type = md.pre_match, md.to_s if column_for_attribute(attribute_name) __send__("attribute#{method_type}", attribute_name, *args, &block) else super end else super end end
Quote strings appropriately for SQL statements.
# File lib/big_record/model.rb, line 578 def quote_value(value, column = nil) self.class.connection.quote(value, column) end
Returns the value of the attribute identified by attr_name
after it has been typecast (for example, “2004-12-12” in a data column is
cast to a date object, like Date.new(2004, 12, 12)).
# File lib/big_record/model.rb, line 687 def read_attribute(attr_name) attr_name = attr_name.to_s if !(value = @attributes[attr_name]).nil? if column = column_for_attribute(attr_name) write_attribute(attr_name, column.type_cast(value)) else value end else nil end end
# File lib/big_record/model.rb, line 700 def read_attribute_before_type_cast(attr_name) @attributes[attr_name.to_s] end
Removes any attributes from the argument hash that have been declared as protected from mass assignment. See the {#attr_protected} and {#attr_accessible} macros to define these attributes.
# File lib/big_record/model.rb, line 638 def remove_attributes_protected_from_mass_assignment(attributes) safe_attributes = if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil? attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) } elsif self.class.protected_attributes.nil? attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) } elsif self.class.accessible_attributes.nil? attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) } else raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both." end if !self.new_record? && !self.class.create_accessible_attributes.nil? safe_attributes = safe_attributes.delete_if{ |key, value| self.class.create_accessible_attributes.include?(key.gsub(/\(.+/,"")) } end removed_attributes = attributes.keys - safe_attributes.keys if removed_attributes.any? log_protected_attribute_removal(removed_attributes) end safe_attributes end
Removes attributes which have been marked as readonly.
# File lib/big_record/model.rb, line 664 def remove_readonly_attributes(attributes) unless self.class.readonly_attributes.nil? attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"")) } else attributes end end
# File lib/big_record/model.rb, line 569 def type_cast_attribute_value(multiparameter_name, value) multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value end
Updates the associated record with values matching those of the instance attributes. Returns the number of affected rows.
# File lib/big_record/model.rb, line 594 def update raise NotImplemented end
Update this record in hbase. Cannot be directly in the method 'update' because it would trigger callbacks and therefore weird behaviors.
# File lib/big_record/model.rb, line 600 def update_bigrecord timestamp = self.respond_to?(:updated_at) ? self.updated_at.to_bigrecord_timestamp : Time.now.to_bigrecord_timestamp connection.update(self.class.table_name, id, clone_in_persistence_format, timestamp) end
# File lib/big_record/model.rb, line 488 def validate_attribute_type(value, column) unless (value == nil) or value.kind_of?(column.klass) raise WrongAttributeDataType, "#{human_attribute_name(column.name)} has the wrong type. Expected #{column.klass}. Record is #{value.class}" end end
Validate the type of the values in the attributes hash
# File lib/big_record/model.rb, line 465 def validate_attributes_schema @attributes.keys.each do |key| value = read_attribute(key) next unless value # type validation if (column = column_for_attribute(key)) and !key.ends_with?(":") if column.collection? unless value.is_a?(Array) raise WrongAttributeDataType, "#{human_attribute_name(column.name)} has the wrong type. Expected collection of #{column.klass}. Record is #{value.class}" end value.each do |v| validate_attribute_type(v, column) end else validate_attribute_type(value, column) end else # Don't save attributes set in a previous schema version @attributes.delete(key) end end end
Private Instance Methods
# File lib/big_record/model.rb, line 771 def convert_number_column_value(value) case value when FalseClass then 0 when TrueClass then 1 when '' then nil else value end end
Define an attribute ? method.
# File lib/big_record/model.rb, line 734 def define_question_method(attr_name) unless attr_name.to_s == self.class.primary_key.to_s self.class.read_methods << "#{attr_name}?" end evaluate_read_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end" end
Define an attribute reader method. Cope with a nil column.
# File lib/big_record/model.rb, line 721 def define_read_method(symbol, attr_name, column) cast_code = column.type_cast_code('v') if column access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']" unless attr_name.to_s == self.class.primary_key.to_s access_code = access_code.insert(0, "raise NoMethodError, 'missing attribute: #{attr_name}', caller unless @attributes.has_key?('#{attr_name}'); ") self.class.read_methods << attr_name end evaluate_read_method attr_name, "def #{symbol}; #{access_code}; end" end
Called on first read access to any given column and generates reader methods for all columns in the ::columns_hash if BigRecord::Base.generate_read_methods is set to true.
# File lib/big_record/model.rb, line 708 def define_read_methods self.class.columns_hash.each do |name, column| unless respond_to_without_attributes?(name) define_read_method(name.to_sym, name, column) end unless respond_to_without_attributes?("#{name}?") define_question_method(name) end end end
Evaluate the definition for an attribute reader or ? method
# File lib/big_record/model.rb, line 743 def evaluate_read_method(attr_name, method_definition) begin self.class.class_eval(method_definition) rescue SyntaxError => err self.class.read_methods.delete(attr_name) if logger logger.warn "Exception occurred during reader method compilation." logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?" logger.warn "#{err.message}" end end end
Updates the attribute identified by attr_name
with the
specified value
. Empty strings for fixnum and float columns
are turned into nil.
# File lib/big_record/model.rb, line 758 def write_attribute(attr_name, value) attr_name = attr_name.to_s column = column_for_attribute(attr_name) raise "Invalid column for this bigrecord object (e.g., you tried to set a predicate value for an entity that is out of the predicate scope)" if column == nil if column.number? @attributes[attr_name] = convert_number_column_value(value) else @attributes[attr_name] = value end end