Class: Sequel::Model
- Defined in:
- lib/sequel/model.rb,
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/relations.rb
Overview
Sequel Models
Models in Sequel are based on the Active Record pattern described by Martin Fowler (www.martinfowler.com/eaaCatalog/activeRecord.html). A model class corresponds to a table or a dataset, and an instance of that class wraps a single record in the model’s underlying dataset.
Model classes are defined as regular Ruby classes:
DB = Sequel('sqlite:/blog.db')
class Post < Sequel::Model
set_dataset DB[:posts]
end
You can also use the shorthand form:
DB = Sequel('sqlite:/blog.db')
class Post < Sequel::Model(:posts)
end
Model instances
Model instance are identified by a primary key. By default, Sequel assumes the primary key column to be :id. The Model#[] method can be used to fetch records by their primary key:
post = Post[123]
The Model#pk method is used to retrieve the record’s primary key value:
post.pk #=> 123
Sequel models allow you to use any column as a primary key, and even composite keys made from multiple columns:
class Post < Sequel::Model(:posts)
set_primary_key [:category, :title]
end
post = Post['ruby', 'hello world']
post.pk #=> ['ruby', 'hello world']
You can also define a model class that does not have a primary key, but then you lose the ability to update records.
A model instance can also be fetched by specifying a condition:
post = Post[:title => 'hello world']
post = Post.find {:stamp < 10.days.ago}
Iterating over records
A model class lets you iterate over specific records by acting as a proxy to the underlying dataset. This means that you can use the entire Dataset API to create customized queries that return model instances, e.g.:
Post.filter(:category => 'ruby').each {|post| p post}
You can also manipulate the records in the dataset:
Post.filter {:stamp < 7.days.ago}.delete
Post.filter {:title =~ /ruby/}.update(:category => 'ruby')
Accessing record values
A model instances stores its values as a hash:
post.values #=> {:id => 123, :category => 'ruby', :title => 'hello world'}
You can read the record values as object attributes:
post.id #=> 123
post.title #=> 'hello world'
You can also change record values:
post.title = 'hey there'
post.save
Another way to change values by using the #set method:
post.set(:title => 'hey there')
Creating new records
New records can be created by calling Model.create:
post = Post.create(:title => 'hello world')
Another way is to construct a new instance and save it:
post = Post.new
post.title = 'hello world'
post.save
You can also supply a block to Model.new and Model.create:
post = Post.create {|p| p.title = 'hello world'}
post = Post.new do |p|
p.title = 'hello world'
p.save
end
Hooks
You can execute custom code when creating, updating, or deleting records by using hooks. The before_create and after_create hooks wrap record creation. The before_update and after_update wrap record updating. The before_save and after_save wrap record creation and updating. The before_destroy and after_destroy wrap destruction.
Hooks are defined by supplying a block:
class Post < Sequel::Model(:posts)
after_create do
set(:created_at => Time.now)
end
after_destroy
author.update_post_count
end
end
Deleting records
You can delete individual records by calling #delete or #destroy. The only difference between the two methods is that #destroy invokes before_destroy and after_destroy hooks, while #delete does not:
post.delete #=> bypasses hooks
post.destroy #=> runs hooks
Records can also be deleted en-masse by invoking Model.delete and Model.destroy. As stated above, you can specify filters for the deleted records:
Post.filter(:category => 32).delete #=> bypasses hooks
Post.filter(:category => 32).destroy #=> runs hooks
Please note that if Model.destroy is called, each record is deleted separately, but Model.delete deletes all relevant records with a single SQL statement.
Associations
The most straightforward way to define an association in a Sequel model is as a regular instance method:
class Post < Sequel::Model(:posts)
def ; Author[]; end
end
class Author < Sequel::Model(:authors)
def posts; Post.filter(:author_id => pk); end
end
Sequel also provides two macros to assist with common types of associations. The one_to_one macro is roughly equivalent to ActiveRecord?‘s belongs_to macro. It defines both getter and setter methods for the association:
class Post < Sequel::Model(:posts)
one_to_one :author, :from => Author
end
post = Post.create(:name => 'hi!')
post. = Author[:name => 'Sharon']
The one_to_many macro is roughly equivalent to ActiveRecord’s has_many macro:
class Author < Sequel::Model(:authors)
one_to_many :posts, :from => Post, :key => :author_id
end
You will have noticed that in some cases the association macros are actually more verbose than hand-coding instance methods. The one_to_one and one_to_many macros also make assumptions (just like ActiveRecord macros) about the database schema which may not be relevant in many cases.
Caching model instances with memcached
Sequel models can be cached using memcached based on their primary keys. The use of memcached can significantly reduce database load by keeping model instances in memory. The set_cache method is used to specify caching:
require 'memcache'
CACHE = MemCache.new 'localhost:11211', :namespace => 'blog'
class Author < Sequel::Model(:authors)
set_cache CACHE, :ttl => 3600
end
Author[333] # database hit
Author[333] # cache hit
Extending the underlying dataset
The obvious way to add table-wide logic is to define class methods to the model class definition. That way you can define subsets of the underlying dataset, change the ordering, or perform actions on multiple records:
class Post < Sequel::Model(:posts)
def self.old_posts
filter {:stamp < 30.days.ago}
end
def self.clean_old_posts
old_posts.delete
end
end
You can also implement table-wide logic by defining methods on the dataset:
class Post < Sequel::Model(:posts)
def dataset.old_posts
filter {:stamp < 30.days.ago}
end
def dataset.clean_old_posts
old_posts.delete
end
end
This is the recommended way of implementing table-wide operations, and allows you to have access to your model API from filtered datasets as well:
Post.filter(:category => 'ruby').clean_old_posts
Sequel models also provide a short hand notation for filters:
class Post < Sequel::Model(:posts)
subset(:old_posts) {:stamp < 30.days.ago}
subset :invisible, :visible => false
end
Defining the underlying schema
Model classes can also be used as a place to define your table schema and control it. The schema DSL is exactly the same provided by Sequel::Schema::Generator:
class Post < Sequel::Model(:posts)
set_schema do
primary_key :id
text :title
text :category
foreign_key :author_id, :table =>
end
end
You can then create the underlying table, drop it, or recreate it:
Post.table_exists?
Post.create_table
Post.drop_table
Post.create_table! # drops the table if it exists and then recreates it
Defined Under Namespace
Classes: ChainBroken
Constant Summary collapse
- VERB_TO_METHOD =
This Hash translates verbs to methodnames used in chain manipulation methods.
{:prepend => :unshift, :append => :push}
- ATTR_RE =
/^([a-zA-Z_]\w*)(=)?$/.freeze
- ID_POSTFIX =
'_id'.freeze
Instance Attribute Summary collapse
-
#changed_columns ⇒ Object
readonly
Returns the value of attribute changed_columns.
-
#values ⇒ Object
readonly
Returns the value of attribute values.
Class Method Summary collapse
- .[](*args) ⇒ Object
-
.after_create(verb = :append, &block) ⇒ Object
Adds block to chain of Hooks for
:after_create
. -
.after_destroy(verb = :append, &block) ⇒ Object
Adds block to chain of Hooks for
:after_destroy
. -
.after_save(verb = :append, &block) ⇒ Object
Adds block to chain of Hooks for
:after_save
. -
.after_update(verb = :append, &block) ⇒ Object
Adds block to chain of Hooks for
:after_update
. -
.all ⇒ Object
Returns an array containing all model records.
-
.before_create(verb = :prepend, &block) ⇒ Object
Adds block to chain of Hooks for
:before_create
. -
.before_destroy(verb = :prepend, &block) ⇒ Object
Adds block to chain of Hooks for
:before_destroy
. -
.before_save(verb = :prepend, &block) ⇒ Object
Adds block to chain of Hooks for
:before_save
. -
.before_update(verb = :prepend, &block) ⇒ Object
Adds block to chain of Hooks for
:before_update
. - .cache_key_from_values(values) ⇒ Object
- .cache_store ⇒ Object
- .cache_ttl ⇒ Object
-
.columns ⇒ Object
Returns the columns in the result set in their original order.
-
.create(values = {}) ⇒ Object
Creates new instance with values set to passed-in Hash ensuring that new? returns true.
-
.create_table ⇒ Object
Creates table.
-
.create_table! ⇒ Object
Like create_table but invokes drop_table when table_exists? is true.
-
.database_opened(db) ⇒ Object
Called when a database is opened in order to automatically associate the first opened database with model classes.
-
.dataset ⇒ Object
Returns the dataset associated with the Model class.
-
.db ⇒ Object
Returns the database associated with the Model class.
-
.db=(db) ⇒ Object
Sets the database associated with the Model class.
-
.delete_all ⇒ Object
Deletes all records.
-
.destroy_all ⇒ Object
Like delete_all, but invokes before_destroy and after_destroy hooks if used.
-
.drop_table ⇒ Object
Drops table.
-
.find(*args, &block) ⇒ Object
Finds a single record according to the supplied filter, e.g.:.
-
.find_or_create(cond) ⇒ Object
Like find but invokes create with given conditions when record does not exists.
- .has_hooks?(key) ⇒ Boolean
-
.hooks ⇒ Object
Returns @hooks which is an instance of Hash with its hook identifier (Symbol) as key and the chain of hooks (Array) as value.
-
.is(plugin, *args) ⇒ Object
(also: is_a)
Loads a plugin for use with the model class, passing optional arguments to the plugin.
- .is_dataset_magic_method?(m) ⇒ Boolean
-
.join(*args) ⇒ Object
Comprehensive description goes here!.
-
.method_missing(m, *args, &block) ⇒ Object
:nodoc:.
-
.no_primary_key ⇒ Object
:nodoc:.
-
.one_to_many(name, opts) ⇒ Object
Creates a 1-N relationship by defining an association method, e.g.:.
-
.one_to_one(name, opts) ⇒ Object
Creates a 1-1 relationship by defining an association method, e.g.:.
-
.plugin_gem(plugin) ⇒ Object
Returns the gem name for the given plugin.
-
.plugin_module(plugin) ⇒ Object
Returns the module for the specified plugin.
-
.primary_key ⇒ Object
Returns key for primary key.
-
.primary_key_hash(value) ⇒ Object
Returns primary key attribute hash.
-
.recreate_table ⇒ Object
Deprecated, use create_table! instead.
-
.schema ⇒ Object
Returns table schema for direct descendant of Model.
-
.serialize(*columns) ⇒ Object
Serializes column with YAML or through marshalling.
- .set_cache(store, opts = {}) ⇒ Object
- .set_cache_ttl(ttl) ⇒ Object
-
.set_dataset(ds) ⇒ Object
Sets the dataset associated with the Model class.
-
.set_primary_key(*key) ⇒ Object
Sets primary key, regular and composite are possible.
-
.set_schema(name = nil, &block) ⇒ Object
Defines a table schema (see Schema::Generator for more information).
-
.subset(name, *args, &block) ⇒ Object
Defines a method that returns a filtered dataset.
-
.super_dataset ⇒ Object
:nodoc:.
-
.table_exists? ⇒ Boolean
Returns true if table exists, false otherwise.
-
.table_name ⇒ Object
Returns name of table.
Instance Method Summary collapse
-
#==(obj) ⇒ Object
Compares model instances by values.
-
#===(obj) ⇒ Object
Compares model instances by pkey.
-
#[](column) ⇒ Object
Returns value of attribute.
-
#[]=(column, value) ⇒ Object
Sets value of attribute and marks the column as changed.
-
#cache_key ⇒ Object
Returns a key unique to the underlying record for caching.
-
#columns ⇒ Object
Returns the columns associated with the object’s Model class.
-
#dataset ⇒ Object
Returns the dataset assoiated with the object’s Model class.
-
#db ⇒ Object
Returns the database assoiated with the object’s Model class.
-
#delete ⇒ Object
Deletes and returns self.
-
#destroy ⇒ Object
Like delete but runs hooks before and after delete.
-
#each(&block) ⇒ Object
Enumerates through all attributes.
-
#exists? ⇒ Boolean
Returns true when current instance exists, false otherwise.
-
#id ⇒ Object
Returns value for
:id
attribute. -
#initialize(values = {}, new_record = false, &block) ⇒ Model
constructor
Creates new instance with values set to passed-in Hash.
-
#keys ⇒ Object
Returns attribute names.
-
#method_missing(m, *args) ⇒ Object
:nodoc:.
-
#new? ⇒ Boolean
Returns true if the current instance represents a new record.
-
#pk ⇒ Object
Returns the primary key value identifying the model instance.
-
#pk_hash ⇒ Object
Returns a hash identifying the model instance.
-
#pkey ⇒ Object
Returns value for primary key.
-
#primary_key ⇒ Object
Returns primary key column(s) for object’s Model class.
-
#refresh ⇒ Object
Reloads values from database and returns self.
-
#run_hooks(key) ⇒ Object
Evaluates specified chain of Hooks through
instance_eval
. -
#save(*columns) ⇒ Object
Creates or updates the associated record.
-
#save_changes ⇒ Object
Saves only changed columns or does nothing if no columns are marked as chanaged.
-
#set(values) ⇒ Object
Updates and saves values to database from the passed-in Hash.
-
#this ⇒ Object
Returns (naked) dataset bound to current instance.
Constructor Details
#initialize(values = {}, new_record = false, &block) ⇒ Model
Creates new instance with values set to passed-in Hash.
This method guesses whether the record exists when new_record
is set to false.
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/sequel/model/record.rb', line 174 def initialize(values = {}, new_record = false, &block) @values = values @changed_columns = [] @new = new_record unless @new # determine if it's a new record k = self.class.primary_key # if there's no primary key for the model class, or # @values doesn't contain a primary key value, then # we regard this instance as new. @new = (k == nil) || (!(Array === k) && !@values[k]) end block[self] if block end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(m, *args) ⇒ Object
:nodoc:
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'lib/sequel/model/record.rb', line 268 def method_missing(m, *args) #:nodoc: if m.to_s =~ ATTR_RE att = $1.to_sym write = $2 == '=' # check whether the column is legal unless columns.include?(att) # if read accessor and a value exists for the column, we return it if !write && @values.has_key?(att) return @values[att] end # otherwise, raise an error raise SequelError, "Invalid column (#{att.inspect}) for #{self}" end # define the column accessor Thread.exclusive do if write model.class_def(m) do |v| @values[att] = v @changed_columns << att unless @changed_columns.include?(att) end else model.class_def(m) {@values[att]} end end # call the accessor respond_to?(m) ? send(m, *args) : super(m, *args) else super(m, *args) end end |
Instance Attribute Details
#changed_columns ⇒ Object (readonly)
Returns the value of attribute changed_columns.
4 5 6 |
# File 'lib/sequel/model/record.rb', line 4 def changed_columns @changed_columns end |
#values ⇒ Object (readonly)
Returns the value of attribute values.
3 4 5 |
# File 'lib/sequel/model/record.rb', line 3 def values @values end |
Class Method Details
.[](*args) ⇒ Object
257 258 259 260 |
# File 'lib/sequel/model.rb', line 257 def self.[](*args) args = args.first if (args.size == 1) dataset[(Hash === args) ? args : primary_key_hash(args)] end |
.after_create(verb = :append, &block) ⇒ Object
Adds block to chain of Hooks for :after_create
. It can either be prepended or appended (default).
Returns the chain itself.
Valid verbs are :prepend
and :append
.
92 93 94 95 |
# File 'lib/sequel/model/hooks.rb', line 92 def self.after_create(verb = :append, &block) hooks[:after_create].send VERB_TO_METHOD.fetch(verb), block if block hooks[:after_create] end |
.after_destroy(verb = :append, &block) ⇒ Object
Adds block to chain of Hooks for :after_destroy
. It can either be prepended or appended (default).
Returns the chain itself.
Valid verbs are :prepend
and :append
.
112 113 114 115 |
# File 'lib/sequel/model/hooks.rb', line 112 def self.after_destroy(verb = :append, &block) hooks[:after_destroy].send VERB_TO_METHOD.fetch(verb), block if block hooks[:after_destroy] end |
.after_save(verb = :append, &block) ⇒ Object
Adds block to chain of Hooks for :after_save
. It can either be prepended or appended (default).
Returns the chain itself.
Valid verbs are :prepend
and :append
.
82 83 84 85 |
# File 'lib/sequel/model/hooks.rb', line 82 def self.after_save(verb = :append, &block) hooks[:after_save].send VERB_TO_METHOD.fetch(verb), block if block hooks[:after_save] end |
.after_update(verb = :append, &block) ⇒ Object
Adds block to chain of Hooks for :after_update
. It can either be prepended or appended (default).
Returns the chain itself.
Valid verbs are :prepend
and :append
.
102 103 104 105 |
# File 'lib/sequel/model/hooks.rb', line 102 def self.after_update(verb = :append, &block) hooks[:after_update].send VERB_TO_METHOD.fetch(verb), block if block hooks[:after_update] end |
.all ⇒ Object
Returns an array containing all model records
304 305 306 |
# File 'lib/sequel/model.rb', line 304 def self.all dataset.all end |
.before_create(verb = :prepend, &block) ⇒ Object
Adds block to chain of Hooks for :before_create
. It can either be prepended (default) or appended.
Returns the chain itself.
Valid verbs are :prepend
and :append
.
51 52 53 54 |
# File 'lib/sequel/model/hooks.rb', line 51 def self.before_create(verb = :prepend, &block) hooks[:before_create].send VERB_TO_METHOD.fetch(verb), block if block hooks[:before_create] end |
.before_destroy(verb = :prepend, &block) ⇒ Object
Adds block to chain of Hooks for :before_destroy
. It can either be prepended (default) or appended.
Returns the chain itself.
Valid verbs are :prepend
and :append
.
71 72 73 74 |
# File 'lib/sequel/model/hooks.rb', line 71 def self.before_destroy(verb = :prepend, &block) hooks[:before_destroy].send VERB_TO_METHOD.fetch(verb), block if block hooks[:before_destroy] end |
.before_save(verb = :prepend, &block) ⇒ Object
Adds block to chain of Hooks for :before_save
. It can either be prepended (default) or appended.
Returns the chain itself.
Valid verbs are :prepend
and :append
.
41 42 43 44 |
# File 'lib/sequel/model/hooks.rb', line 41 def self.before_save(verb = :prepend, &block) hooks[:before_save].send VERB_TO_METHOD.fetch(verb), block if block hooks[:before_save] end |
.before_update(verb = :prepend, &block) ⇒ Object
Adds block to chain of Hooks for :before_update
. It can either be prepended (default) or appended.
Returns the chain itself.
Valid verbs are :prepend
and :append
.
61 62 63 64 |
# File 'lib/sequel/model/hooks.rb', line 61 def self.before_update(verb = :prepend, &block) hooks[:before_update].send VERB_TO_METHOD.fetch(verb), block if block hooks[:before_update] end |
.cache_key_from_values(values) ⇒ Object
38 39 40 |
# File 'lib/sequel/model/caching.rb', line 38 def self.cache_key_from_values(values) "#{self}:#{values.join(',')}" end |
.cache_store ⇒ Object
30 31 32 |
# File 'lib/sequel/model/caching.rb', line 30 def self.cache_store @cache_store end |
.cache_ttl ⇒ Object
34 35 36 |
# File 'lib/sequel/model/caching.rb', line 34 def self.cache_ttl @cache_ttl ||= 3600 end |
.columns ⇒ Object
Returns the columns in the result set in their original order.
See Dataset#columns for more information.
33 34 35 36 |
# File 'lib/sequel/model/base.rb', line 33 def self.columns @columns ||= @dataset.columns or raise SequelError, "Could not fetch columns for #{self}" end |
.create(values = {}) ⇒ Object
Creates new instance with values set to passed-in Hash ensuring that new? returns true.
130 131 132 133 134 135 136 |
# File 'lib/sequel/model/record.rb', line 130 def self.create(values = {}) db.transaction do obj = new(values, true) obj.save obj end end |
.create_table ⇒ Object
Creates table.
31 32 33 |
# File 'lib/sequel/model/schema.rb', line 31 def self.create_table db.create_table_sql_list(*schema.create_info).each {|s| db << s} end |
.create_table! ⇒ Object
Like create_table but invokes drop_table when table_exists? is true.
41 42 43 44 |
# File 'lib/sequel/model/schema.rb', line 41 def self.create_table! drop_table if table_exists? create_table end |
.database_opened(db) ⇒ Object
Called when a database is opened in order to automatically associate the first opened database with model classes.
16 17 18 |
# File 'lib/sequel/model/base.rb', line 16 def self.database_opened(db) @db = db if (self == Model) && !@db end |
.dataset ⇒ Object
Returns the dataset associated with the Model class.
21 22 23 24 |
# File 'lib/sequel/model/base.rb', line 21 def self.dataset @dataset || super_dataset or raise SequelError, "No dataset associated with #{self}" end |
.db ⇒ Object
Returns the database associated with the Model class.
4 5 6 7 |
# File 'lib/sequel/model/base.rb', line 4 def self.db @db ||= (superclass != Object) && superclass.db or raise SequelError, "No database associated with #{self}" end |
.db=(db) ⇒ Object
Sets the database associated with the Model class.
10 11 12 |
# File 'lib/sequel/model/base.rb', line 10 def self.db=(db) @db = db end |
.delete_all ⇒ Object
Deletes all records.
276 277 278 |
# File 'lib/sequel/model.rb', line 276 def self.delete_all dataset.delete end |
.destroy_all ⇒ Object
Like delete_all, but invokes before_destroy and after_destroy hooks if used.
271 272 273 274 |
# File 'lib/sequel/model.rb', line 271 def self.destroy_all has_hooks?(:before_destroy) || has_hooks?(:after_destroy) ? \ dataset.destroy : dataset.delete end |
.drop_table ⇒ Object
Drops table.
36 37 38 |
# File 'lib/sequel/model/schema.rb', line 36 def self.drop_table db.execute db.drop_table_sql(table_name) end |
.find(*args, &block) ⇒ Object
Finds a single record according to the supplied filter, e.g.:
Ticket.find :author => 'Sharon' # => record
Ticket.find {:price == 17} # => Dataset
252 253 254 255 |
# File 'lib/sequel/model.rb', line 252 def self.find(*args, &block) dataset.filter(*args, &block).limit(1).first # dataset[cond.is_a?(Hash) ? cond : primary_key_hash(cond)] end |
.find_or_create(cond) ⇒ Object
Like find but invokes create with given conditions when record does not exists.
264 265 266 |
# File 'lib/sequel/model.rb', line 264 def self.find_or_create(cond) find(cond) || create(cond) end |
.has_hooks?(key) ⇒ Boolean
122 123 124 |
# File 'lib/sequel/model/hooks.rb', line 122 def self.has_hooks?(key) hooks[key] && !hooks[key].empty? end |
.hooks ⇒ Object
Returns @hooks which is an instance of Hash with its hook identifier (Symbol) as key and the chain of hooks (Array) as value.
If it is not already set it’ll be with an empty set of hooks. This behaviour will change in the future to allow inheritance.
For the time being, you should be able to do:
class A < Sequel::Model(:a)
before_save { 'Do something...' }
end
class B < A
@hooks = superclass.hooks.clone
before_save # => [#<Proc:0x0000c6e8@(example.rb):123>]
end
In this case you should remember that the clone doesn’t create any new instances of your chains, so if you change the chain here it changes in its superclass, too.
31 32 33 |
# File 'lib/sequel/model/hooks.rb', line 31 def self.hooks @hooks ||= Hash.new { |h, k| h[k] = [] } end |
.is(plugin, *args) ⇒ Object Also known as: is_a
Loads a plugin for use with the model class, passing optional arguments to the plugin.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/sequel/model/plugins.rb', line 8 def is(plugin, *args) m = plugin_module(plugin) 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") (:"#{plugin}_opts") {args.first} .send(:include, m::ClassMethods) end if m.const_defined?("DatasetMethods") dataset.(:"#{plugin}_opts") {args.first} dataset..send(:include, m::DatasetMethods) end end |
.is_dataset_magic_method?(m) ⇒ Boolean
280 281 282 283 284 285 286 |
# File 'lib/sequel/model.rb', line 280 def self.is_dataset_magic_method?(m) method_name = m.to_s Sequel::Dataset::MAGIC_METHODS.each_key do |r| return true if method_name =~ r end false end |
.join(*args) ⇒ Object
Comprehensive description goes here!
298 299 300 301 |
# File 'lib/sequel/model.rb', line 298 def self.join(*args) table_name = dataset.opts[:from].first dataset.join(*args).select(table_name.to_sym.ALL) end |
.method_missing(m, *args, &block) ⇒ Object
:nodoc:
288 289 290 291 292 293 294 295 |
# File 'lib/sequel/model.rb', line 288 def self.method_missing(m, *args, &block) #:nodoc: Thread.exclusive do if dataset.respond_to?(m) || is_dataset_magic_method?(m) instance_eval("def #{m}(*args, &block); dataset.#{m}(*args, &block); end") end end respond_to?(m) ? send(m, *args, &block) : super(m, *args) end |
.no_primary_key ⇒ Object
:nodoc:
119 120 121 122 123 124 125 126 |
# File 'lib/sequel/model/record.rb', line 119 def self.no_primary_key #:nodoc: (:primary_key) {nil} (:primary_key_hash) {|v| raise SequelError, "#{self} does not have a primary key"} class_def(:this) {raise SequelError, "No primary key is associated with this model"} class_def(:pk) {raise SequelError, "No primary key is associated with this model"} class_def(:pk_hash) {raise SequelError, "No primary key is associated with this model"} class_def(:cache_key) {raise SequelError, "No primary key is associated with this model"} end |
.one_to_many(name, opts) ⇒ Object
Creates a 1-N relationship by defining an association method, e.g.:
class Book < Sequel::Model(:books)
end
class Author < Sequel::Model(:authors)
one_to_many :books, :from => Book
# which is equivalent to
def books
Book.filter(:author_id => id)
end
end
You can also set the foreign key explicitly by including a :key option:
one_to_many :books, :from => Book, :key => :author_id
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/sequel/model/relations.rb', line 82 def self.one_to_many(name, opts) # deprecation if opts[:class] warn "The :class option has been deprecated. Please use :from instead." opts[:from] = opts[:class] end # deprecation if opts[:on] warn "The :on option has been deprecated. Please use :key instead." opts[:key] = opts[:on] end from = opts[:from] from || (raise SequelError, "No association source defined (use :from option)") key = opts[:key] || (self.to_s + ID_POSTFIX).to_sym case from when Symbol class_def(name) {db[from].filter(key => pk)} else class_def(name) {from.filter(key => pk)} end end |
.one_to_one(name, opts) ⇒ Object
Creates a 1-1 relationship by defining an association method, e.g.:
class Session < Sequel::Model(:sessions)
end
class Node < Sequel::Model(:nodes)
one_to_one :producer, :from => Session
# which is equivalent to
def producer
Session[producer_id] if producer_id
end
end
You can also set the foreign key explicitly by including a :key option:
one_to_one :producer, :from => Session, :key => :producer_id
The one_to_one macro also creates a setter, which accepts nil, a hash or a model instance, e.g.:
p = Producer[1234]
node = Node[:path => '/']
node.producer = p
node.producer_id #=> 1234
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/sequel/model/relations.rb', line 30 def self.one_to_one(name, opts) # deprecation if opts[:class] warn "The :class option has been deprecated. Please use :from instead." opts[:from] = opts[:class] end from = opts[:from] from || (raise SequelError, "No association source defined (use :from option)") key = opts[:key] || (name.to_s + ID_POSTFIX).to_sym setter_name = "#{name}=".to_sym case from when Symbol class_def(name) {(k = @values[key]) ? db[from][:id => k] : nil} when Sequel::Dataset class_def(name) {(k = @values[key]) ? from[:id => k] : nil} else class_def(name) {(k = @values[key]) ? from[k] : nil} end class_def(setter_name) do |v| case v when nil set(key => nil) when Sequel::Model set(key => v.pk) when Hash set(key => v[:id]) end end # define_method name, &eval(ONE_TO_ONE_PROC % [key, from]) end |
.plugin_gem(plugin) ⇒ Object
Returns the gem name for the given plugin.
39 40 41 |
# File 'lib/sequel/model/plugins.rb', line 39 def plugin_gem(plugin) "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.
30 31 32 33 34 35 36 |
# File 'lib/sequel/model/plugins.rb', line 30 def plugin_module(plugin) module_name = plugin.to_s.gsub(/(^|_)(.)/) {$2.upcase} if not Sequel::Plugins.const_defined?(module_name) require plugin_gem(plugin) end Sequel::Plugins.const_get(module_name) end |
.primary_key ⇒ Object
Returns key for primary key.
44 45 46 |
# File 'lib/sequel/model/record.rb', line 44 def self.primary_key :id end |
.primary_key_hash(value) ⇒ Object
Returns primary key attribute hash.
49 50 51 |
# File 'lib/sequel/model/record.rb', line 49 def self.primary_key_hash(value) {:id => value} end |
.recreate_table ⇒ Object
Deprecated, use create_table! instead.
47 48 49 50 |
# File 'lib/sequel/model/schema.rb', line 47 def self.recreate_table warn "Model.recreate_table is deprecated. Please use Model.create_table! instead." create_table! end |
.schema ⇒ Object
Returns table schema for direct descendant of Model.
16 17 18 |
# File 'lib/sequel/model/schema.rb', line 16 def self.schema @schema || ((superclass != Model) && (superclass.schema)) end |
.serialize(*columns) ⇒ Object
Serializes column with YAML or through marshalling.
64 65 66 67 68 69 70 71 72 73 |
# File 'lib/sequel/model/base.rb', line 64 def self.serialize(*columns) format = columns.pop[:format] if Hash === columns.last format ||= :yaml @transform = columns.inject({}) do |m, c| m[c] = format m end @dataset.transform(@transform) if @dataset end |
.set_cache(store, opts = {}) ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/sequel/model/caching.rb', line 3 def self.set_cache(store, opts = {}) @cache_store = store if (ttl = opts[:ttl]) set_cache_ttl(ttl) end (:[]) do |*args| if (args.size == 1) && (Hash === (h = args.first)) return dataset[h] end unless obj = @cache_store.get(cache_key_from_values(args)) obj = dataset[primary_key_hash((args.size == 1) ? args.first : args)] @cache_store.set(cache_key_from_values(args), obj, cache_ttl) end obj end class_def(:set) {|v| store.delete(cache_key); super} class_def(:save) {store.delete(cache_key); super} class_def(:delete) {store.delete(cache_key); super} end |
.set_cache_ttl(ttl) ⇒ Object
26 27 28 |
# File 'lib/sequel/model/caching.rb', line 26 def self.set_cache_ttl(ttl) @cache_ttl = ttl end |
.set_dataset(ds) ⇒ Object
Sets the dataset associated with the Model class.
39 40 41 42 43 44 |
# File 'lib/sequel/model/base.rb', line 39 def self.set_dataset(ds) @db = ds.db @dataset = ds @dataset.set_model(self) @dataset.transform(@transform) if @transform end |
.set_primary_key(*key) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/sequel/model/record.rb', line 67 def self.set_primary_key(*key) # if k is nil, we go to no_primary_key if key.empty? || (key.size == 1 && key.first == nil) return no_primary_key end # backwards compat key = (key.length == 1) ? key[0] : key.flatten # redefine primary_key (:primary_key) {key} unless key.is_a? Array # regular primary key class_def(:this) do @this ||= dataset.filter(key => @values[key]).limit(1).naked end class_def(:pk) do @pk ||= @values[key] end class_def(:pk_hash) do @pk ||= {key => @values[key]} end class_def(:cache_key) do pk = @values[key] || (raise SequelError, 'no primary key for this record') @cache_key ||= "#{self.class}:#{pk}" end (:primary_key_hash) do |v| {key => v} end else # composite key exp_list = key.map {|k| "#{k.inspect} => @values[#{k.inspect}]"} block = eval("proc {@this ||= self.class.dataset.filter(#{exp_list.join(',')}).limit(1).naked}") class_def(:this, &block) exp_list = key.map {|k| "@values[#{k.inspect}]"} block = eval("proc {@pk ||= [#{exp_list.join(',')}]}") class_def(:pk, &block) exp_list = key.map {|k| "#{k.inspect} => @values[#{k.inspect}]"} block = eval("proc {@this ||= {#{exp_list.join(',')}}}") class_def(:pk_hash, &block) exp_list = key.map {|k| '#{@values[%s]}' % k.inspect}.join(',') block = eval('proc {@cache_key ||= "#{self.class}:%s"}' % exp_list) class_def(:cache_key, &block) (:primary_key_hash) do |v| key.inject({}) {|m, i| m[i] = v.shift; m} end end 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.
7 8 9 10 11 12 13 |
# File 'lib/sequel/model/schema.rb', line 7 def self.set_schema(name = nil, &block) name ? set_dataset(db[name]) : name = table_name @schema = Schema::Generator.new(db, name, &block) if @schema.primary_key_name set_primary_key @schema.primary_key_name end end |
.subset(name, *args, &block) ⇒ Object
Defines a method that returns a filtered dataset.
243 244 245 |
# File 'lib/sequel/model.rb', line 243 def self.subset(name, *args, &block) dataset.(name) {filter(*args, &block)} end |
.super_dataset ⇒ Object
:nodoc:
26 27 28 |
# File 'lib/sequel/model/base.rb', line 26 def self.super_dataset # :nodoc: superclass.dataset if superclass and superclass.respond_to? :dataset end |
.table_exists? ⇒ Boolean
Returns true if table exists, false otherwise.
26 27 28 |
# File 'lib/sequel/model/schema.rb', line 26 def self.table_exists? db.table_exists?(table_name) end |
.table_name ⇒ Object
Returns name of table.
21 22 23 |
# File 'lib/sequel/model/schema.rb', line 21 def self.table_name dataset.opts[:from].first end |
Instance Method Details
#==(obj) ⇒ Object
Compares model instances by values.
34 35 36 |
# File 'lib/sequel/model/record.rb', line 34 def ==(obj) (obj.class == model) && (obj.values == @values) end |
#===(obj) ⇒ Object
Compares model instances by pkey.
39 40 41 |
# File 'lib/sequel/model/record.rb', line 39 def ===(obj) (obj.class == model) && (obj.pk == pk) end |
#[](column) ⇒ Object
Returns value of attribute.
7 8 9 |
# File 'lib/sequel/model/record.rb', line 7 def [](column) @values[column] end |
#[]=(column, value) ⇒ Object
Sets value of attribute and marks the column as changed.
11 12 13 14 |
# File 'lib/sequel/model/record.rb', line 11 def []=(column, value) @values[column] = value @changed_columns << column unless @changed_columns.include?(column) end |
#cache_key ⇒ Object
Returns a key unique to the underlying record for caching
144 145 146 147 |
# File 'lib/sequel/model/record.rb', line 144 def cache_key pk = @values[:id] || (raise SequelError, 'no primary key for this record') @cache_key ||= "#{self.class}:#{pk}" end |
#columns ⇒ Object
Returns the columns associated with the object’s Model class.
59 60 61 |
# File 'lib/sequel/model/base.rb', line 59 def columns model.columns end |
#dataset ⇒ Object
Returns the dataset assoiated with the object’s Model class.
See Dataset for more information.
54 55 56 |
# File 'lib/sequel/model/base.rb', line 54 def dataset model.dataset end |
#db ⇒ Object
Returns the database assoiated with the object’s Model class.
47 48 49 |
# File 'lib/sequel/model/base.rb', line 47 def db @db ||= model.db end |
#delete ⇒ Object
Deletes and returns self.
261 262 263 264 |
# File 'lib/sequel/model/record.rb', line 261 def delete this.delete self end |
#destroy ⇒ Object
Like delete but runs hooks before and after delete.
252 253 254 255 256 257 258 |
# File 'lib/sequel/model/record.rb', line 252 def destroy db.transaction do run_hooks(:before_destroy) delete run_hooks(:after_destroy) end end |
#each(&block) ⇒ Object
Enumerates through all attributes.
Example:
Ticket.find(7).each { |k, v| puts "#{k} => #{v}" }
20 21 22 |
# File 'lib/sequel/model/record.rb', line 20 def each(&block) @values.each(&block) end |
#exists? ⇒ Boolean
Returns true when current instance exists, false otherwise.
196 197 198 |
# File 'lib/sequel/model/record.rb', line 196 def exists? this.count > 0 end |
#id ⇒ Object
Returns value for :id
attribute.
29 30 31 |
# File 'lib/sequel/model/record.rb', line 29 def id @values[:id] end |
#keys ⇒ Object
Returns attribute names.
24 25 26 |
# File 'lib/sequel/model/record.rb', line 24 def keys @values.keys end |
#new? ⇒ Boolean
Returns true if the current instance represents a new record.
191 192 193 |
# File 'lib/sequel/model/record.rb', line 191 def new? @new end |
#pk ⇒ Object
Returns the primary key value identifying the model instance. Stock implementation.
161 162 163 |
# File 'lib/sequel/model/record.rb', line 161 def pk @pk ||= @values[:id] end |
#pk_hash ⇒ Object
Returns a hash identifying the model instance. Stock implementation.
166 167 168 |
# File 'lib/sequel/model/record.rb', line 166 def pk_hash @pk_hash ||= {:id => @values[:id]} end |
#pkey ⇒ Object
Returns value for primary key.
155 156 157 158 |
# File 'lib/sequel/model/record.rb', line 155 def pkey warn "Model#pkey is deprecated. Please use Model#pk instead." @pkey ||= @values[self.class.primary_key] end |
#primary_key ⇒ Object
Returns primary key column(s) for object’s Model class.
150 151 152 |
# File 'lib/sequel/model/record.rb', line 150 def primary_key @primary_key ||= self.class.primary_key end |
#refresh ⇒ Object
Reloads values from database and returns self.
246 247 248 249 |
# File 'lib/sequel/model/record.rb', line 246 def refresh @values = this.first || raise(SequelError, "Record not found") self end |
#run_hooks(key) ⇒ Object
Evaluates specified chain of Hooks through instance_eval
.
118 119 120 |
# File 'lib/sequel/model/hooks.rb', line 118 def run_hooks(key) model.hooks[key].each {|h| instance_eval(&h)} end |
#save(*columns) ⇒ Object
Creates or updates the associated record. This method can also accept a list of specific columns to update.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/sequel/model/record.rb', line 202 def save(*columns) run_hooks(:before_save) if @new run_hooks(:before_create) 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 run_hooks(:after_create) else run_hooks(:before_update) 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 run_hooks(:after_update) end run_hooks(:after_save) self end |
#save_changes ⇒ Object
Saves only changed columns or does nothing if no columns are marked as chanaged.
235 236 237 |
# File 'lib/sequel/model/record.rb', line 235 def save_changes save(*@changed_columns) unless @changed_columns.empty? end |
#set(values) ⇒ Object
Updates and saves values to database from the passed-in Hash.
240 241 242 243 |
# File 'lib/sequel/model/record.rb', line 240 def set(values) this.update(values) values.each {|k, v| @values[k] = v} end |
#this ⇒ Object
Returns (naked) dataset bound to current instance.
139 140 141 |
# File 'lib/sequel/model/record.rb', line 139 def this @this ||= self.class.dataset.filter(:id => @values[:id]).limit(1).naked end |