Model has some methods that are added via metaprogramming:
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.
# Don't raise an error if a validation attempt fails in # save/create/save_changes/etc. Model.raise_on_save_failure = false Model.before_save{false} Model.new.save # => nil # Don't raise errors in new/set/update/etc. if an attempt to # access a missing/restricted method occurs (just silently # skip it) Model.strict_param_setting = false Model.new(:id=>1) # No Error # Don't typecast attribute values on assignment Model.typecast_on_assignment = false m = Model.new m.number = '10' m.number # => '10' instead of 10 # Don't typecast empty string to nil for non-string, non-blob columns. Model.typecast_empty_string_to_nil = false m.number = '' m.number # => '' instead of nil # Don't raise if unable to typecast data for a column Model.typecast_empty_string_to_nil = true Model.raise_on_typecast_failure = false m.not_null_column = '' # => nil m.number = 'A' # => 'A'
DATASET_METHODS | = | %w'<< all avg count delete distinct eager eager_graph each each_page empty? except exclude filter first from 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 server set set_graph_aliases single_value size to_csv to_hash transform union uniq unfiltered unordered update where'.map{|x| x.to_sym} | Dataset methods to proxy via metaprogramming | |
INHERITED_INSTANCE_VARIABLES | = | {:@allowed_columns=>:dup, :@cache_store=>nil, :@cache_ttl=>nil, :@dataset_methods=>:dup, :@primary_key=>nil, :@raise_on_save_failure=>nil, :@restricted_columns=>:dup, :@restrict_primary_key=>nil, :@sti_dataset=>nil, :@sti_key=>nil, :@strict_param_setting=>nil, :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil, :@raise_on_typecast_failure=>nil, :@association_reflections=>:dup} | Instance variables that are inherited in subclasses | |
HOOKS | = | [:after_initialize, :before_create, :after_create, :before_update, :after_update, :before_save, :after_save, :before_destroy, :after_destroy, :before_validation, :after_validation] | Hooks that are safe for public use | |
PRIVATE_HOOKS | = | [:before_update_values, :before_delete] | Hooks that are only for internal use | |
RESTRICTED_SETTER_METHODS | = | %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure= raise_on_typecast_failure=" | The setter methods (methods ending with =) that are never allowed to be called automatically via set. | |
DEFAULT_VALIDATION_IF_PROC | = | proc{true} | Validations without an :if option are always run |
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.
# File lib/sequel_model/base.rb, line 86 86: def self.[](*args) 87: args = args.first if (args.size == 1) 88: 89: if Hash === args 90: dataset[args] 91: else 92: @cache_store ? cache_lookup(args) : dataset[primary_key_hash(args)] 93: end 94: end
This adds a new hook type. It will define both a class method that you can use to add hooks, as well as an instance method that you can use to call all hooks of that type. The class method can be called with a symbol or a block or both. If a block is given and and symbol is not, it adds the hook block to the hook type. If a block and symbol are both given, it replaces the hook block associated with that symbol for a given hook type, or adds it if there is no hook block with that symbol for that hook type. If no block is given, it assumes the symbol specifies an instance method to call and adds it to the hook type.
If any hook block returns false, the instance method will return false immediately without running the rest of the hooks of that type.
It is recommended that you always provide a symbol to this method, for descriptive purposes. It‘s only necessary to do so when you are using a system that reloads code.
All of Sequel‘s standard hook types are also implemented using this method.
Example of usage:
class MyModel define_hook :before_move_to before_move_to(:check_move_allowed){|o| o.allow_move?} def move_to(there) return if before_move_to == false # move MyModel object to there end end
# File lib/sequel_model/hooks.rb, line 42 42: def self.add_hook_type(*hooks) 43: hooks.each do |hook| 44: @hooks[hook] = [] 45: instance_eval("def #{hook}(method = nil, &block); define_hook_instance_method(:#{hook}); add_hook(:#{hook}, method, &block) end") 46: class_eval("def #{hook}; end") 47: end 48: end
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.
# File lib/sequel_model/base.rb, line 101 101: def self.columns 102: @columns || set_columns(dataset.naked.columns) 103: end
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.
# File lib/sequel_model/base.rb, line 109 109: def self.create(values = {}, &block) 110: obj = new(values, &block) 111: return unless obj.save 112: obj 113: end
Creates table, using the column information from set_schema.
# File lib/sequel_model/schema.rb, line 4 4: def self.create_table 5: db.create_table(table_name, :generator=>@schema) 6: @db_schema = get_db_schema(true) 7: columns 8: end
Drops the table if it exists and then runs create_table. Should probably not be used except in testing.
# File lib/sequel_model/schema.rb, line 12 12: def self.create_table! 13: drop_table rescue nil 14: create_table 15: end
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.
# File lib/sequel_model/base.rb, line 148 148: def self.def_dataset_method(*args, &block) 149: raise(Error, "No arguments given") if args.empty? 150: if block_given? 151: raise(Error, "Defining a dataset method using a block requires only one argument") if args.length > 1 152: meth = args.first 153: @dataset_methods[meth] = block 154: dataset.meta_def(meth, &block) 155: end 156: args.each{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__)} 157: end
Deletes all records in the model‘s table.
# File lib/sequel_model/base.rb, line 160 160: def self.delete_all 161: dataset.delete 162: end
Like delete_all, but invokes before_destroy and after_destroy hooks if used.
# File lib/sequel_model/base.rb, line 165 165: def self.destroy_all 166: dataset.destroy 167: end
Drops table.
# File lib/sequel_model/schema.rb, line 18 18: def self.drop_table 19: db.drop_table(table_name) 20: end
Modify and return eager loading dataset based on association options
# File lib/sequel_model/base.rb, line 175 175: def self.eager_loading_dataset(opts, ds, select, associations) 176: ds = ds.select(*select) if select 177: ds = ds.order(*opts[:order]) if opts[:order] 178: ds = ds.eager(opts[:eager]) if opts[:eager] 179: ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] 180: ds = ds.eager(associations) unless associations.blank? 181: ds = opts[:eager_block].call(ds) if opts[:eager_block] 182: ds 183: end
Finds a single record according to the supplied filter, e.g.:
Ticket.find :author => 'Sharon' # => record
# File lib/sequel_model/base.rb, line 188 188: def self.find(*args, &block) 189: dataset.filter(*args, &block).first 190: end
Returns true if there are any hook blocks for the given hook.
# File lib/sequel_model/hooks.rb, line 51 51: def self.has_hooks?(hook) 52: !@hooks[hook].empty? 53: end
Returns true if validations are defined.
# File lib/sequel_model/validations.rb, line 60 60: def self.has_validations? 61: !validations.empty? 62: end
Yield every block related to the given hook.
# File lib/sequel_model/hooks.rb, line 56 56: def self.hook_blocks(hook) 57: @hooks[hook].each{|k,v| yield v} 58: end
Returns the implicit table name for the model class.
# File lib/sequel_model/base.rb, line 227 227: def self.implicit_table_name 228: name.demodulize.underscore.pluralize.to_sym 229: end
If possible, set the dataset for the model subclass as soon as it is created. Also, inherit the INHERITED_INSTANCE_VARIABLES from the parent class.
# File lib/sequel_model/base.rb, line 201 201: def self.inherited(subclass) 202: sup_class = subclass.superclass 203: ivs = subclass.instance_variables.collect{|x| x.to_s} 204: INHERITED_INSTANCE_VARIABLES.each do |iv, dup| 205: next if ivs.include?(iv.to_s) 206: sup_class_value = sup_class.instance_variable_get(iv) 207: sup_class_value = sup_class_value.dup if dup == :dup && sup_class_value 208: subclass.instance_variable_set(iv, sup_class_value) 209: end 210: unless ivs.include?("@dataset") 211: db 212: begin 213: if sup_class == Model 214: subclass.set_dataset(Model.db[subclass.implicit_table_name]) unless subclass.name.blank? 215: elsif ds = sup_class.instance_variable_get(:@dataset) 216: subclass.set_dataset(sup_class.sti_key ? sup_class.sti_dataset.filter(sup_class.sti_key=>subclass.name.to_s) : ds.clone, :inherited=>true) 217: end 218: rescue 219: nil 220: end 221: end 222: hooks = subclass.instance_variable_set(:@hooks, {}) 223: sup_class.instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup} 224: end
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.
# File lib/sequel_model/plugins.rb, line 21 21: def self.is(plugin, *args) 22: m = plugin_module(plugin) 23: raise(Error, "Plugin cannot be applied because the model class has no dataset") if m.const_defined?("DatasetMethods") && !@dataset 24: if m.respond_to?(:apply) 25: m.apply(self, *args) 26: end 27: if m.const_defined?("InstanceMethods") 28: class_def("#{plugin}_opts""#{plugin}_opts") {args.first} 29: include(m::InstanceMethods) 30: end 31: if m.const_defined?("ClassMethods") 32: meta_def("#{plugin}_opts""#{plugin}_opts") {args.first} 33: extend(m::ClassMethods) 34: end 35: if m.const_defined?("DatasetMethods") 36: dataset.meta_def("#{plugin}_opts""#{plugin}_opts") {args.first} 37: dataset.extend(m::DatasetMethods) 38: def_dataset_method(*m::DatasetMethods.public_instance_methods) 39: end 40: end
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.
# File lib/sequel_model/base.rb, line 235 235: def self.load(values) 236: new(values, true) 237: end
Creates new instance with values set to passed-in Hash. If a block is given, yield the instance to the block unless from_db is true. This method runs the after_initialize hook after it has optionally yielded itself to the block.
Arguments:
# File lib/sequel_model/record.rb, line 26 26: def initialize(values = {}, from_db = false) 27: if from_db 28: @new = false 29: @values = values 30: else 31: @values = {} 32: @new = true 33: set(values) 34: changed_columns.clear 35: yield self if block_given? 36: end 37: after_initialize 38: end
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.
# File lib/sequel_model/base.rb, line 250 250: def self.primary_key_hash(value) 251: raise(Error, "#{self} does not have a primary key") unless key = @primary_key 252: case key 253: when Array 254: hash = {} 255: key.each_with_index{|k,i| hash[k] = value[i]} 256: hash 257: else 258: {key => value} 259: end 260: end
Restrict the setting of the primary key(s) inside new/set/update. Because this is the default, this only make sense to use in a subclass where the parent class has used unrestrict_primary_key.
# File lib/sequel_model/base.rb, line 265 265: def self.restrict_primary_key 266: @restrict_primary_key = true 267: end
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.
# File lib/sequel_model/schema.rb, line 25 25: def self.schema 26: @schema || (superclass.schema unless superclass == Model) 27: end
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.
# File lib/sequel_model/base.rb, line 280 280: def self.serialize(*columns) 281: format = columns.extract_options![:format] || :yaml 282: @transform = columns.inject({}) do |m, c| 283: m[c] = format 284: m 285: end 286: @dataset.transform(@transform) if @dataset 287: end
Set the columns to allow in new/set/update. Using this means that any columns not listed here will not be modified. If you have any virtual setter methods (methods that end in =) that you want to be used in new/set/update, they need to be listed here as well (without the =).
It may be better to use (set|update)_only instead of this in places where only certain columns may be allowed.
# File lib/sequel_model/base.rb, line 301 301: def self.set_allowed_columns(*cols) 302: @allowed_columns = cols 303: end
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
# File lib/sequel_model/caching.rb, line 17 17: def self.set_cache(store, opts = {}) 18: @cache_store = store 19: @cache_ttl = opts[:ttl] || 3600 20: before_save :cache_delete_unless_new 21: before_update_values :cache_delete 22: before_delete :cache_delete 23: end
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.
# File lib/sequel_model/base.rb, line 316 316: def self.set_dataset(ds, opts={}) 317: inherited = opts[:inherited] 318: @dataset = case ds 319: when Symbol 320: db[ds] 321: when Dataset 322: @db = ds.db 323: ds 324: else 325: raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset") 326: end 327: @dataset.set_model(self) 328: @dataset.transform(@transform) if @transform 329: if inherited 330: @columns = @dataset.columns rescue nil 331: else 332: @dataset.extend(DatasetMethods) 333: @dataset.extend(Associations::EagerLoading) 334: @dataset_methods.each{|meth, block| @dataset.meta_def(meth, &block)} if @dataset_methods 335: end 336: @db_schema = (inherited ? superclass.db_schema : get_db_schema) rescue nil 337: self 338: end
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.
# File lib/sequel_model/base.rb, line 356 356: def self.set_primary_key(*key) 357: @primary_key = (key.length == 1) ? key[0] : key.flatten 358: end
Set the columns to restrict in new/set/update. Using this means that any columns listed here will not be modified. If you have any virtual setter methods (methods that end in =) that you want not to be used in new/set/update, they need to be listed here as well (without the =).
It may be better to use (set|update)_except instead of this in places where only certain columns may be allowed.
# File lib/sequel_model/base.rb, line 367 367: def self.set_restricted_columns(*cols) 368: @restricted_columns = cols 369: end
Defines a table schema (see Schema::Generator for more information).
This is only needed if you want to use the create_table/create_table! methods. Will also set the dataset if you provide a name, as well as setting the primary key if you defined one in the passed block.
In general, it is a better idea to use migrations for production code, as migrations allow changes to existing schema. set_schema is mostly useful for test code or simple examples.
# File lib/sequel_model/schema.rb, line 38 38: def self.set_schema(name = nil, &block) 39: set_dataset(db[name]) if name 40: @schema = Schema::Generator.new(db, &block) 41: set_primary_key(@schema.primary_key_name) if @schema.primary_key_name 42: end
Makes this model a polymorphic model with the given key being a string field in the database holding the name of the class to use. If the key given has a NULL value or there are any problems looking up the class, uses the current class.
This should be used to set up single table inheritance for the model, and it only makes sense to use this in the parent class.
You should call sti_key after any calls to set_dataset in the model, otherwise subclasses might not have the filters set up correctly.
The filters that sti_key sets up in subclasses will not work if those subclasses have further subclasses. For those middle subclasses, you will need to call set_dataset manually with the correct filter set.
# File lib/sequel_model/base.rb, line 385 385: def self.set_sti_key(key) 386: m = self 387: @sti_key = key 388: @sti_dataset = dataset 389: dataset.set_model(key, Hash.new{|h,k| h[k] = (k.constantize rescue m)}) 390: before_create(:set_sti_key){send("#{key}=", model.name.to_s)} 391: end
Instructs the model to skip validations defined in superclasses
# File lib/sequel_model/validations.rb, line 65 65: def self.skip_superclass_validations 66: @skip_superclass_validations = true 67: end
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.
# File lib/sequel_model/base.rb, line 397 397: def self.str_columns 398: @str_columns ||= columns.map{|c| c.to_s.freeze} 399: end
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.sql_number > 100) Topic.subset(:recent, :created_on + 7 > Date.today)
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.
# File lib/sequel_model/base.rb, line 415 415: def self.subset(name, *args, &block) 416: def_dataset_method(name){filter(*args, &block)} 417: end
Returns true if table exists, false otherwise.
# File lib/sequel_model/schema.rb, line 45 45: def self.table_exists? 46: db.table_exists?(table_name) 47: end
Allow the setting of the primary key(s) inside new/set/update.
# File lib/sequel_model/base.rb, line 425 425: def self.unrestrict_primary_key 426: @restrict_primary_key = false 427: end
Validates the given instance.
# File lib/sequel_model/validations.rb, line 91 91: def self.validate(o) 92: if superclass.respond_to?(:validate) && !@skip_superclass_validations 93: superclass.validate(o) 94: end 95: validations.each do |att, procs| 96: v = case att 97: when Array 98: att.collect{|a| o.send(a)} 99: else 100: o.send(att) 101: end 102: procs.each {|tag, p| p.call(o, att, v)} 103: end 104: end
Defines validations by converting a longhand block into a series of shorthand definitions. For example:
class MyClass include Validation validates do length_of :name, :minimum => 6 length_of :password, :minimum => 8 end end
is equivalent to:
class MyClass include Validation validates_length_of :name, :minimum => 6 validates_length_of :password, :minimum => 8 end
# File lib/sequel_model/validations.rb, line 86 86: def self.validates(&block) 87: Validation::Generator.new(self, &block) 88: end
Validates acceptance of an attribute. Just checks that the value is equal to the :accept option. This method is unique in that :allow_nil is assumed to be true instead of false.
Possible Options:
# File lib/sequel_model/validations.rb, line 113 113: def self.validates_acceptance_of(*atts) 114: opts = { 115: :message => 'is not accepted', 116: :allow_nil => true, 117: :accept => '1', 118: :tag => :acceptance, 119: }.merge!(atts.extract_options!) 120: atts << opts 121: validates_each(*atts) do |o, a, v| 122: o.errors[a] << opts[:message] unless v == opts[:accept] 123: end 124: end
Validates confirmation of an attribute. Checks that the object has a _confirmation value matching the current value. For example:
validates_confirmation_of :blah
Just makes sure that object.blah = object.blah_confirmation. Often used for passwords or email addresses on web forms.
Possible Options:
# File lib/sequel_model/validations.rb, line 136 136: def self.validates_confirmation_of(*atts) 137: opts = { 138: :message => 'is not confirmed', 139: :tag => :confirmation, 140: }.merge!(atts.extract_options!) 141: atts << opts 142: validates_each(*atts) do |o, a, v| 143: o.errors[a] << opts[:message] unless v == o.send("#{a}_confirmation""#{a}_confirmation") 144: end 145: end
Adds a validation for each of the given attributes using the supplied block. The block must accept three arguments: instance, attribute and value, e.g.:
validates_each :name, :password do |object, attribute, value| object.errors[attribute] << 'is not nice' unless value.nice? end
Possible Options:
# File lib/sequel_model/validations.rb, line 170 170: def self.validates_each(*atts, &block) 171: opts = atts.extract_options! 172: blk = if (i = opts[:if]) || (am = opts[:allow_missing]) || (an = opts[:allow_nil]) || (ab = opts[:allow_blank]) 173: proc do |o,a,v| 174: next if i && !o.instance_eval(&if_proc(opts)) 175: next if an && Array(v).all?{|x| x.nil?} 176: next if ab && Array(v).all?{|x| x.blank?} 177: next if am && Array(a).all?{|x| !o.values.has_key?(x)} 178: block.call(o,a,v) 179: end 180: else 181: block 182: end 183: tag = opts[:tag] 184: atts.each do |a| 185: a_vals = validations[a] 186: if tag && (old = a_vals.find{|x| x[0] == tag}) 187: old[1] = blk 188: else 189: a_vals << [tag, blk] 190: end 191: end 192: end
Validates the format of an attribute, checking the string representation of the value against the regular expression provided by the :with option.
Possible Options:
# File lib/sequel_model/validations.rb, line 200 200: def self.validates_format_of(*atts) 201: opts = { 202: :message => 'is invalid', 203: :tag => :format, 204: }.merge!(atts.extract_options!) 205: 206: unless opts[:with].is_a?(Regexp) 207: raise ArgumentError, "A regular expression must be supplied as the :with option of the options hash" 208: end 209: 210: atts << opts 211: validates_each(*atts) do |o, a, v| 212: o.errors[a] << opts[:message] unless v.to_s =~ opts[:with] 213: end 214: end
Validates the length of an attribute.
Possible Options:
# File lib/sequel_model/validations.rb, line 228 228: def self.validates_length_of(*atts) 229: opts = { 230: :too_long => 'is too long', 231: :too_short => 'is too short', 232: :wrong_length => 'is the wrong length' 233: }.merge!(atts.extract_options!) 234: 235: opts[:tag] ||= ([:length] + [:maximum, :minimum, :is, :within].reject{|x| !opts.include?(x)}).join('-').to_sym 236: atts << opts 237: validates_each(*atts) do |o, a, v| 238: if m = opts[:maximum] 239: o.errors[a] << (opts[:message] || opts[:too_long]) unless v && v.size <= m 240: end 241: if m = opts[:minimum] 242: o.errors[a] << (opts[:message] || opts[:too_short]) unless v && v.size >= m 243: end 244: if i = opts[:is] 245: o.errors[a] << (opts[:message] || opts[:wrong_length]) unless v && v.size == i 246: end 247: if w = opts[:within] 248: o.errors[a] << (opts[:message] || opts[:wrong_length]) unless v && w.include?(v.size) 249: end 250: end 251: end
Validates whether an attribute is not a string. This is generally useful in conjunction with raise_on_typecast_failure = false, where you are passing in string values for non-string attributes (such as numbers and dates). If typecasting fails (invalid number or date), the value of the attribute will be a string in an invalid format, and if typecasting succeeds, the value will not be a string.
Possible Options:
# File lib/sequel_model/validations.rb, line 262 262: def self.validates_not_string(*atts) 263: opts = { 264: :tag => :not_string, 265: }.merge!(atts.extract_options!) 266: atts << opts 267: validates_each(*atts) do |o, a, v| 268: if v.is_a?(String) 269: unless message = opts[:message] 270: message = if sch = o.db_schema[a] and typ = sch[:type] 271: "is not a valid #{typ}" 272: else 273: "is a string" 274: end 275: end 276: o.errors[a] << message 277: end 278: end 279: end
Validates whether an attribute is a number.
Possible Options:
# File lib/sequel_model/validations.rb, line 286 286: def self.validates_numericality_of(*atts) 287: opts = { 288: :message => 'is not a number', 289: :tag => :numericality, 290: }.merge!(atts.extract_options!) 291: atts << opts 292: validates_each(*atts) do |o, a, v| 293: begin 294: if opts[:only_integer] 295: Kernel.Integer(v.to_s) 296: else 297: Kernel.Float(v.to_s) 298: end 299: rescue 300: o.errors[a] << opts[:message] 301: end 302: end 303: end
Validates the presence of an attribute. Requires the value not be blank, with false considered present instead of absent.
Possible Options:
# File lib/sequel_model/validations.rb, line 310 310: def self.validates_presence_of(*atts) 311: opts = { 312: :message => 'is not present', 313: :tag => :presence, 314: }.merge!(atts.extract_options!) 315: atts << opts 316: validates_each(*atts) do |o, a, v| 317: o.errors[a] << opts[:message] if v.blank? && v != false 318: end 319: end
Validates only if the fields in the model (specified by atts) are unique in the database. Pass an array of fields instead of multiple fields to specify that the combination of fields must be unique, instead of that each field should have a unique value.
This means that the code:
validates_uniqueness_of([:column1, :column2])
validates the grouping of column1 and column2 while
validates_uniqueness_of(:column1, :column2)
validates them separately.
You should also add a unique index in the database, as this suffers from a fairly obvious race condition.
Possible Options:
# File lib/sequel_model/validations.rb, line 337 337: def self.validates_uniqueness_of(*atts) 338: opts = { 339: :message => 'is already taken', 340: :tag => :uniqueness, 341: }.merge!(atts.extract_options!) 342: 343: atts << opts 344: validates_each(*atts) do |o, a, v| 345: error_field = a 346: a = Array(a) 347: v = Array(v) 348: ds = o.class.filter(a.zip(v)) 349: num_dups = ds.count 350: allow = if num_dups == 0 351: # No unique value in the database 352: true 353: elsif num_dups > 1 354: # Multiple "unique" values in the database!! 355: # Someone didn't add a unique index 356: false 357: elsif o.new? 358: # New record, but unique value already exists in the database 359: false 360: elsif ds.first === o 361: # Unique value exists in database, but for the same record, so the update won't cause a duplicate record 362: true 363: else 364: false 365: end 366: o.errors[error_field] << opts[:message] unless allow 367: end 368: end
Returns the validations hash for the class.
# File lib/sequel_model/validations.rb, line 371 371: def self.validations 372: @validations ||= Hash.new {|h, k| h[k] = []} 373: end
Compares model instances by values.
# File lib/sequel_model/record.rb, line 59 59: def ==(obj) 60: (obj.class == model) && (obj.values == @values) 61: end
Returns value of the column‘s attribute.
# File lib/sequel_model/record.rb, line 41 41: def [](column) 42: @values[column] 43: end
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.
# File lib/sequel_model/record.rb, line 47 47: def []=(column, value) 48: # If it is new, it doesn't have a value yet, so we should 49: # definitely set the new value. 50: # If the column isn't in @values, we can't assume it is 51: # NULL in the database, so assume it has changed. 52: if new? || !@values.include?(column) || value != @values[column] 53: changed_columns << column unless changed_columns.include?(column) 54: @values[column] = typecast_value(column, value) 55: end 56: end
The current cached associations. A hash with the keys being the association name symbols and the values being the associated object or nil (many_to_one), or the array of associated objects (*_to_many).
# File lib/sequel_model/record.rb, line 79 79: def associations 80: @associations ||= {} 81: end
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.
# File lib/sequel_model/caching.rb, line 64 64: def cache_key 65: raise(Error, "No primary key is associated with this model") unless key = primary_key 66: pk = case key 67: when Array 68: key.collect{|k| @values[k]} 69: else 70: @values[key] || (raise Error, 'no primary key for this record') 71: end 72: model.send(:cache_key, pk) 73: end
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.
# File lib/sequel_model/record.rb, line 101 101: def destroy 102: db.transaction do 103: return save_failure(:destroy) if before_destroy == false 104: delete 105: after_destroy 106: end 107: self 108: end
Enumerates through all attributes.
Example:
Ticket.find(7).each { |k, v| puts "#{k} => #{v}" }
# File lib/sequel_model/record.rb, line 114 114: def each(&block) 115: @values.each(&block) 116: end
Returns true when current instance exists, false otherwise.
# File lib/sequel_model/record.rb, line 119 119: def exists? 120: this.count > 0 121: end
Returns a string representation of the model instance including the class name and values.
# File lib/sequel_model/record.rb, line 137 137: def inspect 138: "#<#{model.name} @values=#{inspect_values}>" 139: end
Returns attribute names as an array of symbols.
# File lib/sequel_model/record.rb, line 142 142: def keys 143: @values.keys 144: end
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.
# File lib/sequel_model/record.rb, line 154 154: def pk 155: raise(Error, "No primary key is associated with this model") unless key = primary_key 156: case key 157: when Array 158: key.collect{|k| @values[k]} 159: else 160: @values[key] 161: end 162: end
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.
# File lib/sequel_model/record.rb, line 174 174: def refresh 175: @values = this.first || raise(Error, "Record not found") 176: changed_columns.clear 177: associations.clear 178: self 179: end
Creates or updates the record, after making sure the record is valid. If the record is not valid, or before_save, before_create (if new?), or before_update (if !new?) return false, returns nil unless raise_on_save_failure is true (if it is true, it raises an error). Otherwise, returns self. You can provide an optional list of columns to update, in which case it only updates those columns.
# File lib/sequel_model/record.rb, line 189 189: def save(*columns) 190: valid? ? save!(*columns) : save_failure(:invalid) 191: end
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 nil unless raise_on_save_failure is true (if it is true, it raises an error). Otherwise, returns self.
# File lib/sequel_model/record.rb, line 199 199: def save!(*columns) 200: opts = columns.extract_options! 201: return save_failure(:save) if before_save == false 202: if new? 203: return save_failure(:create) if before_create == false 204: ds = model.dataset 205: if ds.respond_to?(:insert_select) and h = ds.insert_select(@values) 206: @values = h 207: @this = nil 208: else 209: iid = ds.insert(@values) 210: # if we have a regular primary key and it's not set in @values, 211: # we assume it's the last inserted id 212: if (pk = primary_key) && !(Array === pk) && !@values[pk] 213: @values[pk] = iid 214: end 215: @this = nil if pk 216: end 217: after_create 218: after_save 219: @new = false 220: refresh if pk 221: else 222: return save_failure(:update) if before_update == false 223: if columns.empty? 224: vals = opts[:changed] ? @values.reject{|k,v| !changed_columns.include?(k)} : @values 225: this.update(vals) 226: else # update only the specified columns 227: this.update(@values.reject{|k, v| !columns.include?(k)}) 228: end 229: after_update 230: after_save 231: if columns.empty? 232: changed_columns.clear 233: else 234: changed_columns.reject!{|c| columns.include?(c)} 235: end 236: end 237: self 238: end
Saves only changed columns or does nothing if no columns are marked as chanaged. If no columns have been changed, returns nil. If unable to save, returns false unless raise_on_save_failure is true.
# File lib/sequel_model/record.rb, line 243 243: def save_changes 244: save(:changed=>true) || false unless changed_columns.empty? 245: end
Updates the instance with the supplied values with support for virtual attributes, raising an exception if a value is used that doesn‘t have a setter method (or ignoring it if strict_param_setting = false). 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.
# File lib/sequel_model/record.rb, line 254 254: def set(hash) 255: set_restricted(hash, nil, nil) 256: end
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 instead.
# File lib/sequel_model/record.rb, line 282 282: def set_values(values) 283: s = str_columns 284: vals = values.inject({}) do |m, kv| 285: k, v = kv 286: k = case k 287: when Symbol 288: k 289: when String 290: # Prevent denial of service via memory exhaustion by only 291: # calling to_sym if the symbol already exists. 292: raise(Error, "all string keys must be a valid columns") unless s.include?(k) 293: k.to_sym 294: else 295: raise(Error, "Only symbols and strings allows as keys") 296: end 297: m[k] = v 298: m 299: end 300: vals.each {|k, v| @values[k] = v} 301: vals 302: end
Runs set with the passed hash and runs save_changes (which runs any callback methods).
# File lib/sequel_model/record.rb, line 310 310: def update(hash) 311: update_restricted(hash, nil, nil) 312: end
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.
# File lib/sequel_model/record.rb, line 337 337: def update_values(values) 338: before_update_values 339: this.update(set_values(values)) 340: end