Class: Sequel::Model

Inherits:
Object
  • Object
show all
Extended by:
Enumerable, Associations
Includes:
Validation
Defined in:
lib/sequel_model.rb,
lib/sequel_model/base.rb,
lib/sequel_model/hooks.rb,
lib/sequel_model/record.rb,
lib/sequel_model/schema.rb,
lib/sequel_model/caching.rb,
lib/sequel_model/plugins.rb,
lib/sequel_model/association_reflection.rb

Overview

Model has some methods that are added via metaprogramming:

  • All of the methods in DATASET_METHODS have class methods created that call the Model’s dataset with the method of the same name with the given arguments.

  • All of the methods in HOOKS have class methods created that accept either a method name symbol or an optional tag and a block. These methods run the code as a callback at the specified time. For example:

    Model.before_save :do_something
    Model.before_save(:do_something_else){ self.something_else = 42}
    object = Model.new
    object.save
    

    Would run the object’s :do_something method following by the code block related to :do_something_else. Note that if you specify a block, a tag is optional. If the tag is not nil, it will overwrite a previous block with the same tag. This allows hooks to work with systems that reload code.

  • All of the methods in HOOKS also create instance methods, but you should not override these instance methods.

  • The following instance_methods all call the class method of the same name: columns, dataset, db, primary_key, str_columns.

Defined Under Namespace

Modules: Associations

Constant Summary collapse

DATASET_METHODS =

Dataset methods to proxy via metaprogramming

%w'<< all avg count delete distinct eager eager_graph each each_page 
empty? except exclude filter first from_self full_outer_join get graph 
group group_and_count group_by having import inner_join insert 
insert_multiple intersect interval invert_order join join_table last 
left_outer_join limit map multi_insert naked order order_by order_more 
paginate print query range reverse_order right_outer_join select 
select_all select_more set set_graph_aliases single_value size to_csv 
transform union uniq unordered update where'
HOOKS =

Hooks that are safe for public use

[:after_initialize, :before_create, :after_create, :before_update,
:after_update, :before_save, :after_save, :before_destroy, :after_destroy,
:before_validation, :after_validation]
PRIVATE_HOOKS =

Hooks that are only for internal use

[:before_update_values, :before_delete]
@@lazy_load_schema =

Whether to lazily load the schema for future subclasses. Unless turned off, checks the database for the table schema whenever a subclass is created

false

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Associations

all_association_reflections, associate, association_reflection, associations, many_to_many, many_to_one, one_to_many

Methods included from Validation

#errors, included, #valid?, #validate

Constructor Details

#initialize(values = nil, from_db = false) {|_self| ... } ⇒ Model

Creates new instance with values set to passed-in Hash. If a block is given, yield the instance to the block. This method runs the after_initialize hook after it has optionally yielded itself to the block.

Arguments:

  • values - should be a hash with symbol keys, though string keys will work if from_db is false.

  • from_db - should only be set by Model.load, forget it exists.

Yields:

  • (_self)

Yield Parameters:

  • _self (Sequel::Model)

    the object that the method was called on



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/sequel_model/record.rb', line 28

def initialize(values = nil, from_db = false, &block)
  values ||=  {}
  @changed_columns = []
  @typecast_on_assignment = model.typecast_on_assignment
  @db_schema = model.db_schema
  if from_db
    @new = false
    @values = values
  else
    @values = {}
    @new = true
    set_with_params(values)
  end
  @changed_columns.clear 
  
  yield self if block
  after_initialize
end

Instance Attribute Details

#changed_columnsObject (readonly)

The columns that have been updated. This isn’t completely accurate, see Model#[]=.



7
8
9
# File 'lib/sequel_model/record.rb', line 7

def changed_columns
  @changed_columns
end

#typecast_on_assignment=(value) ⇒ Object (writeonly)

Whether this model instance should typecast on attribute assignment



14
15
16
# File 'lib/sequel_model/record.rb', line 14

def typecast_on_assignment=(value)
  @typecast_on_assignment = value
end

#valuesObject (readonly)

The hash of attribute values. Keys are symbols with the names of the underlying database columns.



11
12
13
# File 'lib/sequel_model/record.rb', line 11

def values
  @values
end

Class Method Details

.[](*args) ⇒ Object

Returns the first record from the database matching the conditions. If a hash is given, it is used as the conditions. If another object is given, it finds the first record whose primary key(s) match the given argument(s). If caching is used, the cache is checked first before a dataset lookup is attempted unless a hash is supplied.

Raises:

  • (Error::InvalidFilter)


38
39
40
41
42
43
44
45
46
47
# File 'lib/sequel_model/base.rb', line 38

def self.[](*args)
  args = args.first if (args.size == 1)
  raise(Error::InvalidFilter, "Did you mean to supply a hash?") if args === true || args === false

  if Hash === args
    dataset[args]
  else
    @cache_store ? cache_lookup(args) : dataset[primary_key_hash(args)]
  end
end

.add_hook(hook, tag, &block) ⇒ Object

Add a hook block to the list of hook methods. If a non-nil tag is given and it already is in the list of hooks, replace it with the new block.



24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/sequel_model/hooks.rb', line 24

def self.add_hook(hook, tag, &block) #:nodoc:
  unless block
    (raise Error, 'No hook method specified') unless tag
    block = proc {send tag}
  end
  h = hooks[hook]
  if tag && (old = h.find{|x| x[0] == tag})
    old[1] = block
  else
    h << [tag, block]
  end
end

.all_hooks(hook) ⇒ Object

Returns all hook methods for the given type of hook for this model class and its ancestors.



39
40
41
# File 'lib/sequel_model/hooks.rb', line 39

def self.all_hooks(hook) # :nodoc:
  ((self == Model ? [] : superclass.send(:all_hooks, hook)) + hooks[hook].collect{|x| x[1]})
end

.cache_delete(key) ⇒ Object

Delete the entry with the matching key from the cache



34
35
36
37
# File 'lib/sequel_model/caching.rb', line 34

def self.cache_delete(key) # :nodoc:
  @cache_store.delete(key)
  nil
end

.cache_key(pk) ⇒ Object

Return a key string for the pk



40
41
42
# File 'lib/sequel_model/caching.rb', line 40

def self.cache_key(pk) # :nodoc:
  "#{self}:#{Array(pk).join(',')}"
end

.cache_lookup(pk) ⇒ Object

Lookup the primary key in the cache. If found, return the matching object. Otherwise, get the matching object from the database and update the cache with it.



48
49
50
51
52
53
54
55
# File 'lib/sequel_model/caching.rb', line 48

def self.cache_lookup(pk) # :nodoc:
  ck = cache_key(pk)
  unless obj = @cache_store.get(ck)
    obj = dataset[primary_key_hash(pk)]
    @cache_store.set(ck, obj, @cache_ttl)
  end
  obj
end

.columnsObject

Returns the columns in the result set in their original order. Generally, this will used the columns determined via the database schema, but in certain cases (e.g. models that are based on a joined dataset) it will use Dataset#columns to find the columns, which may be empty if the Dataset has no records.



54
55
56
# File 'lib/sequel_model/base.rb', line 54

def self.columns
  @columns || set_columns(dataset.naked.columns || raise(Error, "Could not fetch columns for #{self}"))
end

.create(values = {}, &block) ⇒ Object

Creates new instance with values set to passed-in Hash, saves it (running any callbacks), and returns the instance if the object was saved correctly. If there was an error saving the object, returns false.



62
63
64
65
66
# File 'lib/sequel_model/base.rb', line 62

def self.create(values = {}, &block)
  obj = new(values, &block)
  return false if obj.save == false
  obj
end

.create_tableObject

Creates table.



4
5
6
7
8
# File 'lib/sequel_model/schema.rb', line 4

def self.create_table
  db.create_table_sql_list(table_name, *schema.create_info).each {|s| db << s} 
  @db_schema = get_db_schema(true) unless @@lazy_load_schema
  columns
end

.create_table!Object

Drops the table if it exists and then runs create_table.



11
12
13
14
# File 'lib/sequel_model/schema.rb', line 11

def self.create_table!
  drop_table if table_exists?
  create_table
end

.datasetObject

Returns the dataset associated with the Model class.



69
70
71
# File 'lib/sequel_model/base.rb', line 69

def self.dataset
  @dataset || raise(Error, "No dataset associated with #{self}")
end

.dbObject

Returns the database associated with the Model class.

Raises:

  • (Error)


74
75
76
77
78
79
# File 'lib/sequel_model/base.rb', line 74

def self.db
  return @db if @db
  @db = self == Model ? DATABASES.first : superclass.db
  raise(Error, "No database associated with #{self}") unless @db
  @db
end

.db=(db) ⇒ Object

Sets the database associated with the Model class.



82
83
84
85
86
87
# File 'lib/sequel_model/base.rb', line 82

def self.db=(db)
  @db = db
  if @dataset
    set_dataset(db[table_name])
  end
end

.db_schemaObject

Returns the cached schema information if available or gets it from the database.



91
92
93
# File 'lib/sequel_model/base.rb', line 91

def self.db_schema
  @db_schema ||= get_db_schema
end

.def_column_accessor(*columns) ⇒ Object

Create the column accessors



311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/sequel_model/base.rb', line 311

def self.def_column_accessor(*columns) # :nodoc:
  columns.each do |column|
    im = instance_methods
    meth = "#{column}="
     define_method(column){self[column]} unless im.include?(column.to_s)
    unless im.include?(meth)
      define_method(meth) do |*v|
        len = v.length
        raise(ArgumentError, "wrong number of arguments (#{len} for 1)") unless len == 1
        self[column] = v.first 
      end
    end
  end
end

.def_dataset_method(*args, &block) ⇒ Object

If a block is given, define a method on the dataset with the given argument name using the given block as well as a method on the model that calls the dataset method.

If a block is not given, define a method on the model for each argument that calls the dataset method of the same argument name.

Raises:

  • (Error)


101
102
103
104
105
106
107
108
# File 'lib/sequel_model/base.rb', line 101

def self.def_dataset_method(*args, &block)
  raise(Error, "No arguments given") if args.empty?
  if block_given?
    raise(Error, "Defining a dataset method using a block requires only one argument") if args.length > 1
    dataset.meta_def(args.first, &block)
  end
  args.each{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__)}
end

.delete_allObject

Deletes all records in the model’s table.



111
112
113
# File 'lib/sequel_model/base.rb', line 111

def self.delete_all
  dataset.delete
end

.destroy_allObject

Like delete_all, but invokes before_destroy and after_destroy hooks if used.



116
117
118
# File 'lib/sequel_model/base.rb', line 116

def self.destroy_all
  dataset.destroy
end

.drop_tableObject

Drops table.



17
18
19
# File 'lib/sequel_model/schema.rb', line 17

def self.drop_table
  db.execute db.drop_table_sql(table_name)
end

.fetch(*args) ⇒ Object

Returns a dataset with custom SQL that yields model objects.



121
122
123
# File 'lib/sequel_model/base.rb', line 121

def self.fetch(*args)
  db.fetch(*args).set_model(self)
end

.find(*args, &block) ⇒ Object

Finds a single record according to the supplied filter, e.g.:

Ticket.find :author => 'Sharon' # => record


128
129
130
# File 'lib/sequel_model/base.rb', line 128

def self.find(*args, &block)
  dataset.filter(*args, &block).first
end

.find_or_create(cond) ⇒ Object

Like find but invokes create with given conditions when record does not exists.



134
135
136
# File 'lib/sequel_model/base.rb', line 134

def self.find_or_create(cond)
  find(cond) || create(cond)
end

.get_db_schema(reload = false) ⇒ Object

Get the schema from the database, fall back on checking the columns via the database if that will return inaccurate results or if it raises an error.



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/sequel_model/base.rb', line 329

def self.get_db_schema(reload = false) # :nodoc:
  set_columns(nil)
  return nil unless @dataset
  schema_hash = {}
  ds_opts = dataset.opts
  single_table = ds_opts[:from] && (ds_opts[:from].length == 1) \
    && !ds_opts.include?(:join) && !ds_opts.include?(:sql)
  get_columns = proc{columns rescue []}
  if single_table && (schema_array = (db.schema(table_name, :reload=>reload) rescue nil))
    schema_array.each{|k,v| schema_hash[k] = v}
    if ds_opts.include?(:select)
      # Dataset only selects certain columns, delete the other
      # columns from the schema
      cols = get_columns.call
      schema_hash.delete_if{|k,v| !cols.include?(k)}
      cols.each{|c| schema_hash[c] ||= {}}
    else
      # Dataset is for a single table with all columns,
      # so set the columns based on the order they were
      # returned by the schema.
      set_columns(schema_array.collect{|k,v| k})
    end
  else
    # If the dataset uses multiple tables or custom sql or getting
    # the schema raised an error, just get the columns and
    # create an empty schema hash for it.
    get_columns.call.each{|c| schema_hash[c] = {}}
  end
  schema_hash
end

.has_hooks?(key) ⇒ Boolean

Returns true if the model class or any of its ancestors have defined hooks for the given hook key. Notice that this method cannot detect hooks defined using overridden methods.

Returns:

  • (Boolean)


14
15
16
17
# File 'lib/sequel_model/hooks.rb', line 14

def self.has_hooks?(key)
  has = hooks[key] && !hooks[key].empty?
  has || ((self != Model) && superclass.has_hooks?(key))
end

.hooksObject

Returns the hooks hash for this model class.



44
45
46
# File 'lib/sequel_model/hooks.rb', line 44

def self.hooks #:nodoc:
  @hooks ||= Hash.new {|h, k| h[k] = []}
end

.implicit_table_nameObject

Returns the implicit table name for the model class.



159
160
161
# File 'lib/sequel_model/base.rb', line 159

def self.implicit_table_name
  name.demodulize.underscore.pluralize.to_sym
end

.inherited(subclass) ⇒ Object

If possible, set the dataset for the model subclass as soon as it is created. Also, inherit the typecast_on_assignment and primary_key attributes from the parent class.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/sequel_model/base.rb', line 141

def self.inherited(subclass)
  sup_class = subclass.superclass
  ivs = subclass.instance_variables
  subclass.instance_variable_set(:@typecast_on_assignment, sup_class.typecast_on_assignment) unless ivs.include?("@typecast_on_assignment")
  subclass.instance_variable_set(:@primary_key, sup_class.primary_key) unless ivs.include?("@primary_key")
  unless ivs.include?("@dataset")
    begin
      if sup_class == Model
        subclass.set_dataset(Model.db[subclass.implicit_table_name]) unless subclass.name.empty?
      elsif ds = sup_class.instance_variable_get(:@dataset)
        subclass.set_dataset(ds.clone)
      end
    rescue
    end
  end
end

.is(plugin, *args) ⇒ Object

Loads a plugin for use with the model class, passing optional arguments to the plugin. If the plugin has a DatasetMethods module and the model doesn’t have a dataset, raise an Error.

Raises:

  • (Error)


21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/sequel_model/plugins.rb', line 21

def self.is(plugin, *args)
  m = plugin_module(plugin)
  raise(Error, "Plugin cannot be applied because the model class has no dataset") if m.const_defined?("DatasetMethods") && !@dataset
  if m.respond_to?(:apply)
    m.apply(self, *args)
  end
  if m.const_defined?("InstanceMethods")
    class_def(:"#{plugin}_opts") {args.first}
    include(m::InstanceMethods)
  end
  if m.const_defined?("ClassMethods")
    meta_def(:"#{plugin}_opts") {args.first}
    extend(m::ClassMethods)
  end
  if m.const_defined?("DatasetMethods")
    dataset.meta_def(:"#{plugin}_opts") {args.first}
    dataset.metaclass.send(:include, m::DatasetMethods)
    def_dataset_method(*m::DatasetMethods.instance_methods)
  end
end

.lazy_load_schema=(value) ⇒ Object

Set whether to lazily load the schema for future model classes. When the schema is lazy loaded, the schema information is grabbed during the first instantiation of the class instead of when the class is created.



167
168
169
# File 'lib/sequel_model/base.rb', line 167

def self.lazy_load_schema=(value)
  @@lazy_load_schema = value
end

.load(values) ⇒ Object

Initializes a model instance as an existing record. This constructor is used by Sequel to initialize model instances when fetching records. #load requires that values be a hash where all keys are symbols. It probably should not be used by external code.



175
176
177
# File 'lib/sequel_model/base.rb', line 175

def self.load(values)
  new(values, true)
end

.no_primary_keyObject

Mark the model as not having a primary key. Not having a primary key can cause issues, among which is that you won’t be able to update records.



181
182
183
# File 'lib/sequel_model/base.rb', line 181

def self.no_primary_key
  @primary_key = nil
end

.plugin_gem(plugin) ⇒ Object

Returns the gem name for the given plugin.



46
47
48
# File 'lib/sequel_model/plugins.rb', line 46

def self.plugin_gem(plugin) # :nodoc:
  "sequel_#{plugin}"
end

.plugin_module(plugin) ⇒ Object

Returns the module for the specified plugin. If the module is not defined, the corresponding plugin gem is automatically loaded.



52
53
54
55
56
57
58
# File 'lib/sequel_model/plugins.rb', line 52

def self.plugin_module(plugin) # :nodoc:
  module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
  if not Sequel::Plugins.const_defined?(module_name)
    require plugin_gem(plugin)
  end
  Sequel::Plugins.const_get(module_name)
end

.primary_key_hash(value) ⇒ Object

Returns primary key attribute hash. If using a composite primary key value such be an array with values for each primary key in the correct order. For a standard primary key, value should be an object with a compatible type for the key. If the model does not have a primary key, raises an Error.

Raises:

  • (Error)


190
191
192
193
194
195
196
197
198
199
200
# File 'lib/sequel_model/base.rb', line 190

def self.primary_key_hash(value)
  raise(Error, "#{self} does not have a primary key") unless key = @primary_key
  case key
  when Array
    hash = {}
    key.each_with_index{|k,i| hash[k] = value[i]}
    hash
  else
    {key => value}
  end
end

.run_hooks(hook, object) ⇒ Object

Runs all hooks of type hook on the given object. Returns false if any hook returns false.



50
51
52
# File 'lib/sequel_model/hooks.rb', line 50

def self.run_hooks(hook, object) #:nodoc:
  all_hooks(hook).each{|b| return false if object.instance_eval(&b) == false}
end

.schemaObject

Returns table schema created with set_schema for direct descendant of Model. Does not retreive schema information from the database, see db_schema if you want that.



24
25
26
# File 'lib/sequel_model/schema.rb', line 24

def self.schema
  @schema || ((superclass != Model) && (superclass.schema))
end

.serialize(*columns) ⇒ Object

Serializes column with YAML or through marshalling. Arguments should be column symbols, with an optional trailing hash with a :format key set to :yaml or :marshal (:yaml is the default). Setting this adds a transform to the model and dataset so that columns values will be serialized when saved and deserialized when returned from the database.



207
208
209
210
211
212
213
214
# File 'lib/sequel_model/base.rb', line 207

def self.serialize(*columns)
  format = columns.extract_options![:format] || :yaml
  @transform = columns.inject({}) do |m, c|
    m[c] = format
    m
  end
  @dataset.transform(@transform) if @dataset
end

.set_cache(store, opts = {}) ⇒ Object

Set the cache store for the model, as well as the caching before_* hooks.

The cache store should implement the following API:

cache_store.set(key, obj, time) # Associate the obj with the given key
                                # in the cache for the time (specified
                                # in seconds)
cache_store.get(key) => obj # Returns object set with same key
cache_store.get(key2) => nil # nil returned if there isn't an object
                             # currently in the cache with that key


17
18
19
20
21
22
23
# File 'lib/sequel_model/caching.rb', line 17

def self.set_cache(store, opts = {})
  @cache_store = store
  @cache_ttl = opts[:ttl] || 3600
  before_save :cache_delete_unless_new
  before_update_values :cache_delete
  before_delete :cache_delete
end

.set_cache_ttl(ttl) ⇒ Object

Set the time to live for the cache store, in seconds (default is 3600, so 1 hour).



27
28
29
# File 'lib/sequel_model/caching.rb', line 27

def self.set_cache_ttl(ttl)
  @cache_ttl = ttl
end

.set_columns(new_columns) ⇒ Object

Set the columns for this model, reset the str_columns, and create accessor methods for each column.



362
363
364
365
366
367
# File 'lib/sequel_model/base.rb', line 362

def self.set_columns(new_columns) # :nodoc:
  @columns = new_columns
  def_column_accessor(*new_columns) if new_columns
  @str_columns = nil
  @columns
end

.set_dataset(ds) ⇒ Object

Sets the dataset associated with the Model class. ds can be a Symbol (specifying a table name in the current database), or a Dataset. If a dataset is used, the model’s database is changed to the given dataset. If a symbol is used, a dataset is created from the current database with the table name given. Other arguments raise an Error.

This sets the model of the the given/created dataset to the current model and adds a destroy method to it. It also extends the dataset with the Associations::EagerLoading methods, and assigns a transform to it if there is one associated with the model. Finally, it attempts to determine the database schema based on the given/created dataset unless lazy_load_schema is set.



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/sequel_model/base.rb', line 228

def self.set_dataset(ds)
  @dataset = case ds
  when Symbol
    db[ds]
  when Dataset
    @db = ds.db
    ds
  else
    raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset")
  end
  @dataset.set_model(self)
  def_dataset_method(:destroy) do
    raise(Error, "No model associated with this dataset") unless @opts[:models]
    count = 0
    @db.transaction {each {|r| count += 1; r.destroy}}
    count
  end
  @dataset.extend(Associations::EagerLoading)
  @dataset.transform(@transform) if @transform
  begin
    (@db_schema = get_db_schema) unless @@lazy_load_schema
  rescue
  end
  self
end

.set_primary_key(*key) ⇒ Object

Sets primary key, regular and composite are possible.

Example:

class Tagging < Sequel::Model
  # composite key
  set_primary_key :taggable_id, :tag_id
end

class Person < Sequel::Model
  # regular key
  set_primary_key :person_id
end

You can set it to nil to not have a primary key, but that cause certain things not to work, see #no_primary_key.



270
271
272
# File 'lib/sequel_model/base.rb', line 270

def self.set_primary_key(*key)
  @primary_key = (key.length == 1) ? key[0] : key.flatten
end

.set_schema(name = nil, &block) ⇒ Object

Defines a table schema (see Schema::Generator for more information).

This is only needed if you want to use the create_table or drop_table methods.



32
33
34
35
36
# File 'lib/sequel_model/schema.rb', line 32

def self.set_schema(name = nil, &block)
  set_dataset(db[name]) if name
  @schema = Schema::Generator.new(db, &block)
  set_primary_key(@schema.primary_key_name) if @schema.primary_key_name
end

.str_columnsObject

Returns the columns as a list of frozen strings instead of a list of symbols. This makes it possible to check whether a column exists without creating a symbol, which would be a memory leak if called with user input.



278
279
280
# File 'lib/sequel_model/base.rb', line 278

def self.str_columns
  @str_columns ||= columns.map{|c| c.to_s.freeze}
end

.subset(name, *args, &block) ⇒ Object

Defines a method that returns a filtered dataset. Subsets create dataset methods, so they can be chained for scoping. For example:

Topic.subset(:popular, :num_posts > 100)
Topic.subset(:recent, :created_on > Date.today - 7)

Allows you to do:

Topic.filter(:username.like('%joe%')).popular.recent

to get topics with a username that includes joe that have more than 100 posts and were created less than 7 days ago.



296
297
298
# File 'lib/sequel_model/base.rb', line 296

def self.subset(name, *args, &block)
  def_dataset_method(name){filter(*args, &block)}
end

.table_exists?Boolean

Returns true if table exists, false otherwise.

Returns:

  • (Boolean)


39
40
41
# File 'lib/sequel_model/schema.rb', line 39

def self.table_exists?
  db.table_exists?(table_name)
end

.table_nameObject

Returns name of primary table for the dataset.



301
302
303
# File 'lib/sequel_model/base.rb', line 301

def self.table_name
  dataset.opts[:from].first
end

Instance Method Details

#==(obj) ⇒ Object Also known as: eql?

Compares model instances by values.



66
67
68
# File 'lib/sequel_model/record.rb', line 66

def ==(obj)
  (obj.class == model) && (obj.values == @values)
end

#===(obj) ⇒ Object

If pk is not nil, true only if the objects have the same class and pk. If pk is nil, false.



73
74
75
# File 'lib/sequel_model/record.rb', line 73

def ===(obj)
  pk.nil? ? false : (obj.class == model) && (obj.pk == pk)
end

#[](column) ⇒ Object

Returns value of the column’s attribute.



48
49
50
# File 'lib/sequel_model/record.rb', line 48

def [](column)
  @values[column]
end

#[]=(column, value) ⇒ Object

Sets value of the column’s attribute and marks the column as changed. If the column already has the same value, this is a no-op.



54
55
56
57
58
59
60
61
62
63
# File 'lib/sequel_model/record.rb', line 54

def []=(column, value)
  # If it is new, it doesn't have a value yet, so we should
  # definitely set the new value.
  # If the column isn't in @values, we can't assume it is
  # NULL in the database, so assume it has changed.
  if new? || !@values.include?(column) || value != @values[column]
    @changed_columns << column unless @changed_columns.include?(column)
    @values[column] = typecast_value(column, value)
  end
end

#cache_keyObject

Return a key unique to the underlying record for caching, based on the primary key value(s) for the object. If the model does not have a primary key, raise an Error.

Raises:

  • (Error)


64
65
66
67
68
69
70
71
72
73
# File 'lib/sequel_model/caching.rb', line 64

def cache_key
  raise(Error, "No primary key is associated with this model") unless key = primary_key
  pk = case key
  when Array
    key.collect{|k| @values[k]}
  else
    @values[key] || (raise Error, 'no primary key for this record')
  end
  model.send(:cache_key, pk)
end

#deleteObject

Deletes and returns self. Does not run destroy hooks. Look into using destroy instead.



85
86
87
88
89
# File 'lib/sequel_model/record.rb', line 85

def delete
  before_delete
  this.delete
  self
end

#destroyObject

Like delete but runs hooks before and after delete. If before_destroy returns false, returns false without deleting the object the the database. Otherwise, deletes the item from the database and returns self.



95
96
97
98
99
100
101
102
# File 'lib/sequel_model/record.rb', line 95

def destroy
  db.transaction do
    return false if before_destroy == false
    delete
    after_destroy
  end
  self
end

#each(&block) ⇒ Object

Enumerates through all attributes.

Example:

Ticket.find(7).each { |k, v| puts "#{k} => #{v}" }


108
109
110
# File 'lib/sequel_model/record.rb', line 108

def each(&block)
  @values.each(&block)
end

#exists?Boolean

Returns true when current instance exists, false otherwise.

Returns:

  • (Boolean)


113
114
115
# File 'lib/sequel_model/record.rb', line 113

def exists?
  this.count > 0
end

#hashObject

Unique for objects with the same class and pk (if pk is not nil), or the same class and values (if pk is nil).



119
120
121
# File 'lib/sequel_model/record.rb', line 119

def hash
  [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
end

#idObject

Returns value for the :id attribute, even if the primary key is not id. To get the primary key value, use #pk.



125
126
127
# File 'lib/sequel_model/record.rb', line 125

def id
  @values[:id]
end

#inspectObject

Returns a string representation of the model instance including the class name and values.



131
132
133
# File 'lib/sequel_model/record.rb', line 131

def inspect
  "#<#{model.name} @values=#{@values.inspect}>"
end

#keysObject

Returns attribute names as an array of symbols.



136
137
138
# File 'lib/sequel_model/record.rb', line 136

def keys
  @values.keys
end

#new?Boolean

Returns true if the current instance represents a new record.

Returns:

  • (Boolean)


141
142
143
# File 'lib/sequel_model/record.rb', line 141

def new?
  @new
end

#pkObject

Returns the primary key value identifying the model instance. Raises an error if this model does not have a primary key. If the model has a composite primary key, returns an array of values.

Raises:

  • (Error)


148
149
150
151
152
153
154
155
156
# File 'lib/sequel_model/record.rb', line 148

def pk
  raise(Error, "No primary key is associated with this model") unless key = primary_key
  case key
  when Array
    key.collect{|k| @values[k]}
  else
    @values[key]
  end
end

#pk_hashObject

Returns a hash identifying the model instance. It should be true that:

Model[model_instance.pk_hash] === model_instance


161
162
163
# File 'lib/sequel_model/record.rb', line 161

def pk_hash
  model.primary_key_hash(pk)
end

#refreshObject Also known as: reload

Reloads attributes from database and returns self. Also clears all cached association information. Raises an Error if the record no longer exists in the database.



168
169
170
171
172
173
174
# File 'lib/sequel_model/record.rb', line 168

def refresh
  @values = this.first || raise(Error, "Record not found")
  model.all_association_reflections.each do |r|
    instance_variable_set("@#{r[:name]}", nil)
  end
  self
end

#save(*columns) ⇒ Object

Creates or updates the record, after making sure the record is valid. If the record is not valid, returns false. If before_save, before_create (if new?), or before_update (if !new?) return false, returns false. Otherwise, returns self.



182
183
184
185
# File 'lib/sequel_model/record.rb', line 182

def save(*columns)
  return false unless valid?
  save!(*columns)
end

#save!(*columns) ⇒ Object

Creates or updates the record, without attempting to validate it first. You can provide an optional list of columns to update, in which case it only updates those columns. If before_save, before_create (if new?), or before_update (if !new?) return false, returns false. Otherwise, returns self.



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/sequel_model/record.rb', line 193

def save!(*columns)
  return false if before_save == false
  if @new
    return false if before_create == false
    iid = model.dataset.insert(@values)
    # if we have a regular primary key and it's not set in @values,
    # we assume it's the last inserted id
    if (pk = primary_key) && !(Array === pk) && !@values[pk]
      @values[pk] = iid
    end
    if pk
      @this = nil # remove memoized this dataset
      refresh
    end
    @new = false
    after_create
  else
    return false if before_update == false
    if columns.empty?
      this.update(@values)
      @changed_columns = []
    else # update only the specified columns
      this.update(@values.reject {|k, v| !columns.include?(k)})
      @changed_columns.reject! {|c| columns.include?(c)}
    end
    after_update
  end
  after_save
  self
end

#save_changesObject

Saves only changed columns or does nothing if no columns are marked as chanaged.



226
227
228
# File 'lib/sequel_model/record.rb', line 226

def save_changes
  save(*@changed_columns) unless @changed_columns.empty?
end

#set_values(values) ⇒ Object

Sets the value attributes without saving the record. Returns the values changed. Raises an error if the keys are not symbols or strings or a string key was passed that was not a valid column. This is a low level method that does not respect virtual attributes. It should probably be avoided. Look into using set_with_params instead.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/sequel_model/record.rb', line 235

def set_values(values)
  s = str_columns
  vals = values.inject({}) do |m, kv| 
    k, v = kv
    k = case k
    when Symbol
      k
    when String
      # Prevent denial of service via memory exhaustion by only 
      # calling to_sym if the symbol already exists.
      raise(Error, "all string keys must be a valid columns") unless s.include?(k)
      k.to_sym
    else
      raise(Error, "Only symbols and strings allows as keys")
    end
    m[k] = v
    m
  end
  vals.each {|k, v| @values[k] = v}
  vals
end

#set_with_params(hash) ⇒ Object

Updates the instance with the supplied values with support for virtual attributes, ignoring any values for which no setter method is available. Does not save the record.

If no columns have been set for this model (very unlikely), assume symbol keys are valid column names, and assign the column value based on that.



263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/sequel_model/record.rb', line 263

def set_with_params(hash)
  columns_not_set = model.instance_variable_get(:@columns).blank?
  meths = setter_methods
  hash.each do |k,v|
    m = "#{k}="
    if meths.include?(m)
      send(m, v)
    elsif columns_not_set && (Symbol === k)
      self[k] = v
    end
  end
end

#thisObject

Returns (naked) dataset that should return only this instance.



277
278
279
# File 'lib/sequel_model/record.rb', line 277

def this
  @this ||= dataset.filter(pk_hash).limit(1).naked
end

#update_values(values) ⇒ Object

Sets the values attributes with set_values and then updates the record in the database using those values. This is a low level method that does not run the usual save callbacks. It should probably be avoided. Look into using update_with_params instead.



285
286
287
288
# File 'lib/sequel_model/record.rb', line 285

def update_values(values)
  before_update_values
  this.update(set_values(values))
end

#update_with_params(values) ⇒ Object

Runs set_with_params and runs save_changes (which runs any callback methods).



291
292
293
294
# File 'lib/sequel_model/record.rb', line 291

def update_with_params(values)
  set_with_params(values)
  save_changes
end