Class: FlexiRecord::BaseRecord

Inherits:
AbstractRecord show all
Includes:
MonitorMixin
Defined in:
lib/flexirecord.rb

Overview

A record representing a row of a database table or query result.

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_symbol, *arguments) ⇒ Object

Provides easy access to the fields of the record.



1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
# File 'lib/flexirecord.rb', line 1018

def method_missing(method_symbol, *arguments)
  synchronize do
    attr = method_symbol.to_s
    if attr[-1, 1] == '='
      attr = attr[0, attr.length-1]
      mode = :write
      value = arguments.pop
    else
      mode = :read
    end
    reader                = self.class.reader(attr)
    loader                = self.class.loader(attr)
    table_column_existent = self.class.columns.include?(attr)
    if mode == :write
      data_hash_key        = [attr] + arguments
      setter = self.class.setter(attr)
      if setter
        setter.call(self, value)
        return nil
      elsif @data_hash.has_key?(data_hash_key) or reader or loader or table_column_existent
        @data_hash[data_hash_key] = value
        return nil
      end
    elsif mode == :read
      self.class.prepare_read_parameters(attr, arguments)
      data_hash_key = [attr] + arguments
      if reader
        return reader.call(self, arguments)
      elsif @data_hash.has_key?(data_hash_key)
        return @data_hash[data_hash_key]
      elsif loader
        loader.call(FlexiRecord::RecordArray.new(self.class, [self]), arguments)
        unless @data_hash.has_key?(data_hash_key)
          puts data_hash_key.inspect
          raise "Record loader failed."
        end
        return @data_hash[data_hash_key]
      elsif table_column_existent
        unless arguments.empty?
          raise ArgumentError, "Attribute getter method does not support arguments."
        end
        return nil
      end
    end
    return super
  end
end

Class Method Details

.add_connected_references(destination_class1, column_info1, src_to_dst_attr1, dst_to_src_attr1, class1_to_class2_attr, destination_class2, column_info2, src_to_dst_attr2, dst_to_src_attr2, class2_to_class1_attr) ⇒ Object

Adds two ManyToOneReferences and connects them to form a many-to-many relation between two other record classes. Please look at the FlexiRecordDemo module to see how this works.



847
848
849
# File 'lib/flexirecord.rb', line 847

def add_connected_references(destination_class1, column_info1, src_to_dst_attr1, dst_to_src_attr1, class1_to_class2_attr, destination_class2, column_info2, src_to_dst_attr2, dst_to_src_attr2, class2_to_class1_attr)
  self.add_many_to_one_reference(destination_class1, column_info1, src_to_dst_attr1, dst_to_src_attr1).combine(self.add_many_to_one_reference(destination_class2, column_info2, src_to_dst_attr2, dst_to_src_attr2), class1_to_class2_attr, class2_to_class1_attr)
end

.add_many_to_one_reference(destination_class, *arguments) ⇒ Object

Adds a ManyToOneReference to the class (by simply creating it). The first argument is the destination class, followed by arguments being passed to Reference.new.



840
841
842
843
844
# File 'lib/flexirecord.rb', line 840

def add_many_to_one_reference(destination_class, *arguments)
  return FlexiRecord::ManyToOneReference.new(
    self, destination_class, *arguments
  )
end

.add_one_to_one_reference(destination_class, *arguments) ⇒ Object

Adds an OneToOneReference to the class (by simply creating it). The first argument is the destination class, followed by arguments being passed to Reference.new.



833
834
835
836
837
# File 'lib/flexirecord.rb', line 833

def add_one_to_one_reference(destination_class, *arguments)
  return FlexiRecord::OneToOneReference.new(
    self, destination_class, *arguments
  )
end

.add_read_option(attr, symbol, value) ⇒ Object

Adds a “shortcut” for a parameter to reader and loader functions.

Example: List.add_read_option :items, :by_name, ‘ORDER BY “name”’



769
770
771
772
773
774
# File 'lib/flexirecord.rb', line 769

def add_read_option(attr, symbol, value)
  unless symbol.kind_of? Symbol
    raise "Symbol expected as second argument to 'add_read_option'."
  end
  (@read_options ||= {})[[attr.to_s, symbol]] = value
end

.after_select(records) ⇒ Object

This method is used on each Array of records being selected from the database via the ‘sql’ or ‘select’ method. It does nothing, but can be extended to automatically preload certain fields for example.



690
691
# File 'lib/flexirecord.rb', line 690

def after_select(records)
end

.autodetect_columnsObject

Autodetects the columns (and primary key columns) of the underlaying table, to be later retrieved by the ‘columns’ and the ‘primary_columns’ methods.



650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
# File 'lib/flexirecord.rb', line 650

def autodetect_columns
  columns = []
  primary_columns = []
  db_query('SELECT ' <<
      '"pg_attribute"."attname", ' <<
      '"pg_constraint"."conkey" @> ARRAY["pg_attribute"."attnum"] AS "primary" ' <<
      'FROM "pg_attribute" ' <<
      'JOIN "pg_class" ON "pg_attribute"."attrelid" = "pg_class"."oid" ' <<
      'JOIN "pg_namespace" ON "pg_class"."relnamespace" = "pg_namespace"."oid" ' <<
      'LEFT JOIN "pg_constraint" ON "pg_class"."oid" = "pg_constraint"."conrelid" ' <<
      'WHERE "pg_attribute"."attnum" > 0 AND "pg_attribute"."attisdropped" = FALSE ' <<
      'AND "pg_class"."relname" = $ ' <<
      'AND "pg_namespace"."nspname" = $ ' <<
      'AND "pg_constraint"."contype" = $ ' <<
      'ORDER BY "attnum"',
      table_name!, schema_name!, 'p').each do |attribute_record|
      attribute_record.attname.freeze
    columns << attribute_record.attname
    primary_columns << attribute_record.attname if attribute_record.primary
  end
  @primary_columns = primary_columns.freeze
  @columns = columns.freeze
  nil
end

.columnsObject

Returns an array of columns (String’s) of the underlaying table. The columns may be autodetected (and cached) at the first call of this method.



676
677
678
679
680
# File 'lib/flexirecord.rb', line 676

def columns
  return [].freeze if table_name.nil?
  autodetect_columns if @columns.nil?
  return @columns
end

.connection_poolObject

Returns the connection pool being used for this class in general.



573
574
575
# File 'lib/flexirecord.rb', line 573

def connection_pool
  @connection_pool
end

.connection_pool=(pool) ⇒ Object

Sets the connection pool to use for this class in general.



568
569
570
# File 'lib/flexirecord.rb', line 568

def connection_pool=(pool)
  @connection_pool = pool
end

.db_execute(command_template, *command_arguments) ⇒ Object

Executes the given SQL command with optional arguments on the database being used to store objects of this class. Returns nil.



728
729
730
731
732
# File 'lib/flexirecord.rb', line 728

def db_execute(command_template, *command_arguments)
  use_connection do |connection|
    return connection.execute(command_template, *command_arguments)
  end
end

.db_query(command_template, *command_arguments) ⇒ Object

Executes the given SQL query with optional arguments on the database being used to store objects of this class. Returns an array of BaseRecord’s (but NOT sub-classes of BaseRecord) containing the data of all rows of the query result.



716
717
718
719
720
# File 'lib/flexirecord.rb', line 716

def db_query(command_template, *command_arguments)
  use_connection do |connection|
    return connection.query(command_template, *command_arguments)
  end
end

.db_query1(*arguments) ⇒ Object

Executes the given SQL query with optional arguments on the database being used to store objects of this class. Returns an array of BaseRecord’s (but NOT sub-classes of BaseRecord) containing the data of the first row of the query result.



723
724
725
# File 'lib/flexirecord.rb', line 723

def db_query1(*arguments)
  db_query(*arguments).first
end

.isolation_levelObject

Returns the isolation_level of a transaction in progress on the connection used for accessing the table of this class.



622
623
624
625
626
# File 'lib/flexirecord.rb', line 622

def isolation_level
  use_connection do |connection|
    return connection.isolation_level
  end
end

.loader(attr) ⇒ Object

Returns the loader function (a Proc object) for a certain attribute.



818
819
820
# File 'lib/flexirecord.rb', line 818

def loader(attr)
  (@loaders || {})[attr.to_s] or ((superclass <= FlexiRecord::BaseRecord) ? superclass.loader(attr) : nil)
end

.lock(mode) ⇒ Object

Locks the table used to store objects of this class. Calling this command only makes sense, if a transaction is in progress.



639
640
641
642
643
644
645
646
647
# File 'lib/flexirecord.rb', line 639

def lock(mode)
  if not mode.kind_of? FlexiRecord::LockMode and mode.respond_to? :to_sym
    mode = FlexiRecord::LockMode.by_symbol(mode)
  end
  if mode.nil?
    raise "Table lock mode expected"
  end
  db_execute("LOCK #{table} IN #{mode}")
end

.prepare_read_parameters(attr, parameters) ⇒ Object

Modifies a parameter array by replacing “shortcut” symbols being defined by add_read_option. Returns the parameter array.



782
783
784
785
786
787
788
789
790
791
# File 'lib/flexirecord.rb', line 782

def prepare_read_parameters(attr, parameters)
  option = parameters.first
  value = read_option_value(attr, option.nil? ? :default : option)
  if value
    parameters[0] = value
  elsif option == :default
    parameters.shift
  end
  return parameters
end

.primary_columnsObject

Returns an array of columns (String’s) being part of the primary key of the underlaying table. (This will be in most cases an Array with one entry.) The columns may be autodetected (and cached) at the first call of this method.



683
684
685
686
687
# File 'lib/flexirecord.rb', line 683

def primary_columns
  return [].freeze if table_name.nil?
  autodetect_columns if @primary_columns.nil?
  return @primary_columns
end

.read_option_value(attr, symbol) ⇒ Object

Returns the value of a “shortcut” for a parameter to reader and loader functions.



777
778
779
# File 'lib/flexirecord.rb', line 777

def read_option_value(attr, symbol)
  value = (@read_options || {})[[attr.to_s, symbol]] || ((superclass <= FlexiRecord::BaseRecord) ? superclass.read_option_value(attr, symbol) : nil)
end

.reader(attr) ⇒ Object

Returns the reader function (a Proc object) for a certain attribute.



799
800
801
# File 'lib/flexirecord.rb', line 799

def reader(attr)
  (@readers || {})[attr.to_s] or ((superclass <= FlexiRecord::BaseRecord) ? superclass.reader(attr) : nil)
end

.reader_attrsObject

Returns an array containing all attributes having a reader Proc stored in the class.



804
805
806
807
808
809
810
# File 'lib/flexirecord.rb', line 804

def reader_attrs
  (@readers || {}).keys + (
    (superclass <= FlexiRecord::BaseRecord) ?
    superclass.reader_attrs :
    []
  )
end

.schema_nameObject

Returns the database schema name of this class (or of it’s superclass, if it has no own schema name)



532
533
534
535
536
537
538
# File 'lib/flexirecord.rb', line 532

def schema_name
  @schema_name or (
    (superclass <= FlexiRecord::BaseRecord) ?
    superclass.schema_name :
    nil
  )
end

.schema_name!Object

Returns the database schema name, even if no schema is set (in this case “public” is returned).



541
542
543
# File 'lib/flexirecord.rb', line 541

def schema_name!
  schema_name or "public".freeze
end

.schema_name=(schema_name) ⇒ Object

Sets the database schema name for this class.



522
523
524
# File 'lib/flexirecord.rb', line 522

def schema_name=(schema_name)
  @schema_name = schema_name ? schema_name.to_s.dup.freeze : nil
end

.select(sql_snippet = nil, *arguments) ⇒ Object

Wrapper for the ‘sql’ method including already a part of the SQL select command. Please see the source code to understand how this method works.



735
736
737
# File 'lib/flexirecord.rb', line 735

def select(sql_snippet=nil, *arguments)
  sql("SELECT #{FlexiRecord::DefaultTableAlias}.* FROM #{table} #{FlexiRecord::DefaultTableAlias}#{sql_snippet ? ' ' : ''}#{sql_snippet}", *arguments)
end

.select1(*arguments) ⇒ Object

Same as ‘select’, but returns only the first member of the Array, or nil.



740
741
742
# File 'lib/flexirecord.rb', line 740

def select1(*arguments)
  select(*arguments).first
end

.select_by_value_set(keys, set_of_values, sql_snippet = nil, *arguments) ⇒ Object

Executes an SQL query, selecting rows matching a given set of keys and values, optionally appending a given SQL snippet with parameters. Returns an Array of records.



745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
# File 'lib/flexirecord.rb', line 745

def select_by_value_set(keys, set_of_values, sql_snippet=nil, *arguments)
  flattened_values = set_of_values.to_ary.flatten
  if flattened_values.empty?
    return sql(nil)
  else
    if sql_snippet
      return sql(
        'SELECT ' << FlexiRecord::DefaultTableAlias << '.* FROM (' <<
        'SELECT * FROM ' << table << ' WHERE (' << keys.collect { |key| '"' << key << '"' }.join(', ') << ') ' << 'IN (' << set_of_values.collect { |values| '(' << values.collect { |value| '$' }.join(', ') << ')' }.join(', ') << ')' <<
        ') AS ' << FlexiRecord::DefaultTableAlias << ' ' << sql_snippet.to_s,
        *(flattened_values + arguments)
      )
    else
      return sql(
        'SELECT * FROM ' << table << ' WHERE (' << keys.collect { |key| '"' << key << '"' }.join(', ') << ') ' << 'IN (' << set_of_values.collect { |values| '(' << values.collect { |value| '$' }.join(', ') << ')' }.join(', ') << ')',
        *(flattened_values + arguments)
      )
    end
  end
end

.set_loader(attr, &loader) ⇒ Object

Sets a given block to be the “loader function” for a particular attribute. Loader functions are invoked, when you try to read an uncached value of the given attribute of a record, or when you preload data for a whole Array of records. Two arguments are passed to the block. The first is an Array of records, whose data is to be loaded, the second is an Array of arguments passed to the method used for accessing the value. The block has to evaluate to an Array of records, which can be used for more than one level deep preloads.



813
814
815
# File 'lib/flexirecord.rb', line 813

def set_loader(attr, &loader)
  (@loaders ||= {})[attr.to_s] = loader
end

.set_reader(attr, &reader) ⇒ Object

Sets a given block to be the “reader function” for a particlular attribute. A reader function is invoked, when you try to read a value of the given attribute of a record. Two arguments are passed to the block. The first is the record, whose data is to be read, the second is an Array of arguments passed to the method used for reading the value. The block has to evaluate to the value which should be read.



794
795
796
# File 'lib/flexirecord.rb', line 794

def set_reader(attr, &reader)
  (@readers ||= {})[attr.to_s] = reader
end

.set_setter(attr, &setter) ⇒ Object

Sets a given block to be the “setter function” for a particular attribute. The setter function is invoked, when you set the value of the given attribute of a record. Two arguments are passed to the block. The first is the record, whose data is to be changed, the second is the new value to be written into the field.



823
824
825
# File 'lib/flexirecord.rb', line 823

def set_setter(attr, &setter)
  (@setters ||= {})[attr.to_s] = setter
end

.setter(attr) ⇒ Object

Returns the setter function (a Proc object) for a certain attribute.



828
829
830
# File 'lib/flexirecord.rb', line 828

def setter(attr)
  (@setters || {})[attr.to_s] or ((superclass <= FlexiRecord::BaseRecord) ? superclass.setter(attr) : nil)
end

.sql(command_template, *command_arguments) ⇒ Object

Executes the given SQL query with optional arguments on the database being used to store objects of this class. Returns an array of objects of the class this method is used on (containing the data of all rows of the query result).



694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
# File 'lib/flexirecord.rb', line 694

def sql(command_template, *command_arguments)
  records = nil
  if command_template
    transaction(:unless_open, :serializable) do
      use_connection do |connection|
        records = connection.record_query(self, command_template, *command_arguments)
      end
      after_select(records)
    end
  else
    records = FlexiRecord::RecordArray.new(self)
    after_select(records)
  end
  return records
end

.sql1(*arguments) ⇒ Object

Executes the given SQL query with optional arguments on the database being used to store objects of this class. Returns a single object of the class this method is used on (containing the data of the first row of the query result.)



711
712
713
# File 'lib/flexirecord.rb', line 711

def sql1(*arguments)
  sql(*arguments).first
end

.tableObject

Returns an SQL snippet including the quoted schema and table name.



561
562
563
564
565
# File 'lib/flexirecord.rb', line 561

def table
  schema_name ?
    %Q("#{schema_name}"."#{table_name!}") :
    %Q("#{table_name!}")
end

.table_nameObject

Returns the table name of this class (or of it’s superclass, if it has no own table name)



546
547
548
549
550
551
552
# File 'lib/flexirecord.rb', line 546

def table_name
  @table_name or (
    (superclass <= FlexiRecord::BaseRecord) ?
    superclass.table_name :
    nil
  )
end

.table_name!Object

Returns the table name, or raises an error, if no name is found.



555
556
557
558
# File 'lib/flexirecord.rb', line 555

def table_name!
  table_name or
    raise "No table name set for #{self.name}."
end

.table_name=(table_name) ⇒ Object

Sets the database table name for this class. This must only be used on sub-classes of BaseRecord, and never on BaseRecord itself.



527
528
529
# File 'lib/flexirecord.rb', line 527

def table_name=(table_name)
  @table_name = table_name ? table_name.to_s.dup.freeze : nil
end

.thread_connection_poolObject

Returns the ConnectionPool being used for the current thread, if an explicit pool was set for the current thread.



589
590
591
# File 'lib/flexirecord.rb', line 589

def thread_connection_pool
  (Thread.current[:flexirecord_thread_connection_pools] ||= {})[self]
end

.thread_connection_pool=(pool) ⇒ Object

Sets the ConnectionPool to use for this class in the current thread.



578
579
580
581
582
583
584
585
586
# File 'lib/flexirecord.rb', line 578

def thread_connection_pool=(pool)
  pool_hash = Thread.current[:flexirecord_thread_connection_pools] ||= {}
  if pool.nil?
    pool_hash.delete(self)
  else
    pool_hash[self] = pool
  end
  nil
end

.transaction(*arguments) ⇒ Object

Wraps the given block in a transaction of the database being used to store objects of this class. See FlexiRecord::Connection#transaction for details of the command.



629
630
631
632
633
634
635
636
# File 'lib/flexirecord.rb', line 629

def transaction(*arguments)
  use_connection do |connection|
    connection.transaction(*arguments) do
      return yield
    end
  end
  result
end

.transaction?Boolean

Returns true, if a transaction is in progress on the connection used for accessing the table of this class.

Returns:

  • (Boolean)


615
616
617
618
619
# File 'lib/flexirecord.rb', line 615

def transaction?
  use_connection do |connection|
    return connection.transaction?
  end
end

.use_connectionObject

Calls the given block with a Connection object to the database being used to store objects of this class.



594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
# File 'lib/flexirecord.rb', line 594

def use_connection
  pool = nil
  catch :found do
    current_class = self
    while current_class <= FlexiRecord::BaseRecord
      throw :found if pool = current_class.thread_connection_pool
      current_class = current_class.superclass
    end
    current_class = self
    while current_class <= FlexiRecord::BaseRecord
      throw :found if pool = current_class.connection_pool
      current_class = current_class.superclass
    end
    raise "No connection pool set for #{self.name}."
  end
  pool.use_connection do |connection|
    return yield(connection)
  end
end

Instance Method Details

#[](*key) ⇒ Object

Reads a value (whose key can consist of multiple fields) from the internal cache. The first argument is by convention usually a name of a column as String(!), but NOT as a Symbol.



920
921
922
923
924
# File 'lib/flexirecord.rb', line 920

def [](*key)
  synchronize do
    return @data_hash[key]
  end
end

#[]=(*arguments) ⇒ Object

Writes a value to the internal cache. The first argument is by convention usually a name of a column as String(!), but NOT as a Symbol.



927
928
929
930
931
932
933
# File 'lib/flexirecord.rb', line 927

def []=(*arguments)
  synchronize do
    value = arguments.pop
    key   = arguments
    return @data_hash[key] = value
  end
end

#delete_from_cache(*key) ⇒ Object

Deletes an entry from the internal cache, and returns it’s value.



943
944
945
946
947
# File 'lib/flexirecord.rb', line 943

def delete_from_cache(*key)
  synchronize do
    return @data_hash.delete(key)
  end
end

#destroyObject

Destroys the record in the database, by executing a DELETE command.



1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
# File 'lib/flexirecord.rb', line 1124

def destroy
  if self.saved?
    self.class.db_execute('DELETE FROM ' << self.class.table <<
      ' WHERE ' << (self.class.primary_columns.collect { |column| '"' << column << '" = $' }.join(' AND ')),
      *( self.class.primary_columns.collect { |column| @old_primary_key[column] } )
    )
    @saved = false
  end
  return self
end

#dupObject

Duplicates a record, including it’s internal state.



902
903
904
905
906
907
908
# File 'lib/flexirecord.rb', line 902

def dup
  synchronize do
    duplicate = super
    duplicate.dup_internal_state
    return duplicate
  end
end

#has_key?(*key) ⇒ Boolean

Returns true, if the internal cache has stored the specified entry, otherwise false.

Returns:

  • (Boolean)


936
937
938
939
940
# File 'lib/flexirecord.rb', line 936

def has_key?(*key)
  synchronize do
    return @data_hash.has_key?(key)
  end
end

#inspectObject

Returns a string representation of the record for debugging purposes.



991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
# File 'lib/flexirecord.rb', line 991

def inspect
  synchronize do
    processed_objects = Thread.current[:flexirecord_baserecord_inspect_cycle_check] ||= {}
    if processed_objects[self]
      return "#<#{self.class}:0x#{sprintf "%08x", object_id}"
    else
      begin
        processed_objects[self] = true
        return "#<#{self.class}:0x#{sprintf "%08x", object_id} #{@saved ? 'saved' : 'unsaved'}, old_primary_key = {" <<
          self.class.primary_columns.dup.delete_if { |column| not @old_primary_key.has_key?(column) }.
          collect { |column| column.inspect << '=>' << @old_primary_key[column].inspect }.join(', ') << "}, data = {" <<
          self.class.columns.dup.delete_if { |column| not (@data_hash.has_key?([column]) or self.class.reader(column)) }.
          collect { |column| column.inspect << '=>' << read(column).inspect }.join(', ') << "}>"
      ensure
        processed_objects.delete(self)
      end
    end
  end
end

#read(attr, *arguments) ⇒ Object

Reads an attribute. If there is a dynamic reader, this reader is used, otherwise an existent assigned loader function is invoked or the internal cache will give a result. If there is no data for the attribute with the given name, then nil will be returned.



950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
# File 'lib/flexirecord.rb', line 950

def read(attr, *arguments)
  attr = attr.to_s
  self.class.prepare_read_parameters(attr, arguments)
  data_hash_key = [attr] + arguments
  reader = self.class.reader(attr)
  loader = self.class.loader(attr)
  synchronize do
    if reader
      return reader.call(self, arguments)
    elsif @data_hash.has_key?(data_hash_key)
      return @data_hash[data_hash_key]
    elsif loader
      loader.call(FlexiRecord::RecordArray.new(self.class, [self]), arguments)
      unless @data_hash.has_key?(data_hash_key)
        raise "Record loader failed."
      end
    end
    return @data_hash[data_hash_key]
  end
end

#reloadObject

Reloads the record from the database. It can not be used on records, which have not been saved to the database yet.



1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
# File 'lib/flexirecord.rb', line 1136

def reload
  synchronize do
    if self.class.primary_columns.empty?
      raise "Can not reload a record, which has no primary key."
    end
    unless self.saved?
      raise "Can not reload a record, which has not been saved yet."
    end
    reloaded_record = self.class.db_query1(
      'SELECT * FROM ' << self.class.table <<
      'WHERE ' << (self.class.primary_columns.collect { |column| '"' << column << '" = $' }.join(' AND ')),
      *(
        self.class.primary_columns.collect { |column| @old_primary_key[column] }
      )
    )
    if reloaded_record.nil?
      raise DatabaseError, "Could not reload data."
    end
    new_data_hash = {}
    self.class.columns.each { |column| new_data_hash[[column]] = reloaded_record.read(column) }
    @data_hash = new_data_hash
    return self
  end
end

#replace(backup) ⇒ Object

Replaces the internal state with the state of a backup. This method is needed for transaction rollbacks.



911
912
913
914
915
916
917
# File 'lib/flexirecord.rb', line 911

def replace(backup)
  synchronize do
    raise TypeError, "Can not restore backup of objects of other classes." unless backup.class == self.class
    @data_hash, @saved, @old_primary_key = backup.read_internal_state
    return self
  end
end

#saveObject

Saves the record in the database, either by INSERT’ing or UPDATE’ing it.



1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
# File 'lib/flexirecord.rb', line 1074

def save
  synchronize do
    used_columns = self.used_columns
    primary_key = nil
    if @saved
      if self.class.primary_columns.empty?
        raise "Can not re-save a record of a table without a primary key."
      end
      primary_key = self.class.db_query1(
        'UPDATE ' << self.class.table <<
        ' SET ' << (used_columns.collect { |column| '"' << column << '" = $' }.join(', ')) <<
        ' WHERE ' << (self.class.primary_columns.collect { |column| '"' << column << '" = $' }.join(' AND ')) <<
        ' RETURNING ' << (self.class.primary_columns.collect { |column| '"' << column << '"' }.join(', ')),
        *(
          used_columns.collect { |column| read(column) } +
          self.class.primary_columns.collect { |column| @old_primary_key[column] }
        )
      )
    else
      if used_columns.empty?
        primary_key = self.class.db_query1('INSERT INTO ' << self.class.table << ' DEFAULT VALUES' <<
        (self.class.primary_columns.empty? ? '' : (
          ' RETURNING ' << (self.class.primary_columns.collect { |column| '"' << column << '"' }.join(', '))
        )))
      else
        primary_key = self.class.db_query1(
          'INSERT INTO ' << self.class.table <<
          ' (' << (used_columns.collect { |column| '"' << column << '"' }.join(', ')) << ')' <<
          ' VALUES (' << (used_columns.collect { |column| '$' }.join(', ')) << ')' <<
          (self.class.primary_columns.empty? ? '' : (
            ' RETURNING ' << (self.class.primary_columns.collect { |column| '"' << column << '"' }.join(', '))
          )),
          *(
            used_columns.collect { |column| read(column) }
          )
        )
      end
      @saved = true
    end
    unless primary_key.nil?
      self.class.primary_columns.each do |column|
        self.set(column, primary_key.read(column))
      end
    end
    copy_primary_key
    return self
  end
end

#saved?Boolean

Returns true, if the record has been saved in database once, otherwise false.

Returns:

  • (Boolean)


1067
1068
1069
1070
1071
# File 'lib/flexirecord.rb', line 1067

def saved?
  synchronize do
    @saved
  end
end

#set(attr, value) ⇒ Object

Sets an attribute. If there is a dynamic setter, this setter is used, otherwise the value get’s written in the internal cache.



972
973
974
975
976
977
978
979
980
981
# File 'lib/flexirecord.rb', line 972

def set(attr, value)
  attr = attr.to_s
  setter = self.class.setter(attr)
  if setter
    setter.call(self, value)
    return @data_hash[[attr]]
  else
    return @data_hash[[attr]] = value
  end
end

#to_sObject

Alias for the inspect method.



1012
1013
1014
# File 'lib/flexirecord.rb', line 1012

def to_s
  inspect
end

#transaction(*arguments, &block) ⇒ Object

Wraps the given block in a transaction of the database being used to store the object. See FlexiRecord::Connection#transaction for details of the command. This object is automatically passed to the FlexiRecord::Connection#transaction method, to rollback changes, in case an Error is raised.



1162
1163
1164
# File 'lib/flexirecord.rb', line 1162

def transaction(*arguments, &block)
  self.class.transaction(self, *arguments, &block)
end

#update(data) ⇒ Object

Rewrites several attributes given by the keys and values in the ‘data’ hash. Note: A SQL UPDATE command is not executed before ‘save’ is called, this method just updates the ruby object. Returns self.



894
895
896
897
898
899
# File 'lib/flexirecord.rb', line 894

def update(data)
  synchronize do
    data.each { |key, value| self.set(key, value) }
    return self
  end
end

#used_columnsObject

Returns an array of strings of the columns in the backend database, which have either values in the internal cache, or which have a dynamic reader function.



984
985
986
987
988
# File 'lib/flexirecord.rb', line 984

def used_columns
  synchronize do
    return self.class.columns & (self.class.reader_attrs + @data_hash.keys.reject { |key| key.length > 1 }.collect { |key| key.first })
  end
end