Class: Sequel::Model
- Inherits:
-
Object
- Object
- Sequel::Model
- 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
-
#changed_columns ⇒ Object
readonly
The columns that have been updated.
-
#typecast_on_assignment ⇒ Object
writeonly
Whether this model instance should typecast on attribute assignment.
-
#values ⇒ Object
readonly
The hash of attribute values.
Class Method Summary collapse
-
.[](*args) ⇒ Object
Returns the first record from the database matching the conditions.
-
.add_hook(hook, tag, &block) ⇒ Object
Add a hook block to the list of hook methods.
-
.all_hooks(hook) ⇒ Object
Returns all hook methods for the given type of hook for this model class and its ancestors.
-
.cache_delete(key) ⇒ Object
Delete the entry with the matching key from the cache.
-
.cache_key(pk) ⇒ Object
Return a key string for the pk.
-
.cache_lookup(pk) ⇒ Object
Lookup the primary key in the cache.
-
.columns ⇒ Object
Returns the columns in the result set in their original order.
-
.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.
-
.create_table ⇒ Object
Creates table.
-
.create_table! ⇒ Object
Drops the table if it exists and then runs create_table.
-
.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.
-
.db_schema ⇒ Object
Returns the cached schema information if available or gets it from the database.
-
.def_column_accessor(*columns) ⇒ Object
Create the column accessors.
-
.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.
-
.delete_all ⇒ Object
Deletes all records in the model’s table.
-
.destroy_all ⇒ Object
Like delete_all, but invokes before_destroy and after_destroy hooks if used.
-
.drop_table ⇒ Object
Drops table.
-
.fetch(*args) ⇒ Object
Returns a dataset with custom SQL that yields model objects.
-
.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.
-
.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.
-
.has_hooks?(key) ⇒ Boolean
Returns true if the model class or any of its ancestors have defined hooks for the given hook key.
-
.hooks ⇒ Object
Returns the hooks hash for this model class.
-
.implicit_table_name ⇒ Object
Returns the implicit table name for the model class.
-
.inherited(subclass) ⇒ Object
If possible, set the dataset for the model subclass as soon as it is created.
-
.is(plugin, *args) ⇒ Object
Loads a plugin for use with the model class, passing optional arguments to the plugin.
-
.lazy_load_schema=(value) ⇒ Object
Set whether to lazily load the schema for future model classes.
-
.load(values) ⇒ Object
Initializes a model instance as an existing record.
-
.no_primary_key ⇒ Object
Mark the model as not having a primary key.
-
.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_hash(value) ⇒ Object
Returns primary key attribute hash.
-
.run_hooks(hook, object) ⇒ Object
Runs all hooks of type hook on the given object.
-
.schema ⇒ Object
Returns table schema created with set_schema for direct descendant of Model.
-
.serialize(*columns) ⇒ Object
Serializes column with YAML or through marshalling.
-
.set_cache(store, opts = {}) ⇒ Object
Set the cache store for the model, as well as the caching before_* hooks.
-
.set_cache_ttl(ttl) ⇒ Object
Set the time to live for the cache store, in seconds (default is 3600, so 1 hour).
-
.set_columns(new_columns) ⇒ Object
Set the columns for this model, reset the str_columns, and create accessor methods for each column.
-
.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).
-
.str_columns ⇒ Object
Returns the columns as a list of frozen strings instead of a list of symbols.
-
.subset(name, *args, &block) ⇒ Object
Defines a method that returns a filtered dataset.
-
.table_exists? ⇒ Boolean
Returns true if table exists, false otherwise.
-
.table_name ⇒ Object
Returns name of primary table for the dataset.
Instance Method Summary collapse
-
#==(obj) ⇒ Object
(also: #eql?)
Compares model instances by values.
-
#===(obj) ⇒ Object
If pk is not nil, true only if the objects have the same class and pk.
-
#[](column) ⇒ Object
Returns value of the column’s attribute.
-
#[]=(column, value) ⇒ Object
Sets value of the column’s attribute and marks the column as changed.
-
#cache_key ⇒ Object
Return a key unique to the underlying record for caching, based on the primary key value(s) for the object.
-
#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.
-
#hash ⇒ Object
Unique for objects with the same class and pk (if pk is not nil), or the same class and values (if pk is nil).
-
#id ⇒ Object
Returns value for the :id attribute, even if the primary key is not id.
-
#initialize(values = nil, from_db = false) {|_self| ... } ⇒ Model
constructor
Creates new instance with values set to passed-in Hash.
-
#inspect ⇒ Object
Returns a string representation of the model instance including the class name and values.
-
#keys ⇒ Object
Returns attribute names as an array of symbols.
-
#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.
-
#refresh ⇒ Object
(also: #reload)
Reloads attributes from database and returns self.
-
#save(*columns) ⇒ Object
Creates or updates the record, after making sure the record is valid.
-
#save!(*columns) ⇒ Object
Creates or updates the record, without attempting to validate it first.
-
#save_changes ⇒ Object
Saves only changed columns or does nothing if no columns are marked as chanaged.
-
#set_values(values) ⇒ Object
Sets the value attributes without saving the record.
-
#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.
-
#this ⇒ Object
Returns (naked) dataset that should return only this instance.
-
#update_values(values) ⇒ Object
Sets the values attributes with set_values and then updates the record in the database using those values.
-
#update_with_params(values) ⇒ Object
Runs set_with_params and runs save_changes (which runs any callback methods).
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.
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_columns ⇒ Object (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 |
#values ⇒ Object (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.
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 |
.columns ⇒ Object
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_table ⇒ Object
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 |
.dataset ⇒ Object
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 |
.db ⇒ Object
Returns the database associated with the Model class.
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_schema ⇒ Object
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.
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.(args.first, &block) end args.each{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__)} end |
.delete_all ⇒ Object
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_all ⇒ Object
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_table ⇒ Object
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.
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 |
.hooks ⇒ Object
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_name ⇒ Object
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.
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") (:"#{plugin}_opts") {args.first} extend(m::ClassMethods) end if m.const_defined?("DatasetMethods") dataset.(:"#{plugin}_opts") {args.first} dataset..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_key ⇒ Object
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.
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 |
.schema ⇒ Object
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.[: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_columns ⇒ Object
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.
39 40 41 |
# File 'lib/sequel_model/schema.rb', line 39 def self.table_exists? db.table_exists?(table_name) end |
.table_name ⇒ Object
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_key ⇒ Object
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.
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 |
#delete ⇒ Object
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 |
#destroy ⇒ Object
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.
113 114 115 |
# File 'lib/sequel_model/record.rb', line 113 def exists? this.count > 0 end |
#hash ⇒ Object
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 |
#id ⇒ Object
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 |
#inspect ⇒ Object
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 |
#keys ⇒ Object
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.
141 142 143 |
# File 'lib/sequel_model/record.rb', line 141 def new? @new end |
#pk ⇒ Object
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.
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_hash ⇒ Object
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 |
#refresh ⇒ Object 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_changes ⇒ Object
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 |
#this ⇒ Object
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 |