Class: Ohm::Model

Inherits:
Object show all
Includes:
Model::Validations
Defined in:
lib/ohm.rb

Defined Under Namespace

Modules: Validations Classes: Collection, Index, IndexNotFound, List, MissingID, Set, Wrapper

Constant Summary

@@attributes =
Hash.new { |hash, key| hash[key] = [] }
@@collections =
Hash.new { |hash, key| hash[key] = [] }
@@counters =
Hash.new { |hash, key| hash[key] = [] }
@@indices =
Hash.new { |hash, key| hash[key] = [] }

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Methods included from Model::Validations

#assert_unique

Methods included from Validations

#assert, #assert_format, #assert_numeric, #assert_present, #errors, #valid?, #validate

Constructor Details

- (Model) initialize(attrs = {})

A new instance of Model



563
564
565
566
# File 'lib/ohm.rb', line 563

def initialize(attrs = {})
  @_attributes = Hash.new { |hash, key| hash[key] = read_remote(key) }
  update_attributes(attrs)
end

Instance Attribute Details

- (Object) id



323
324
325
# File 'lib/ohm.rb', line 323

def id
  @id or raise MissingID
end

Class Method Details

+ (Object) [](id)



512
513
514
# File 'lib/ohm.rb', line 512

def self.[](id)
  new(:id => id) if exists?(id)
end

+ (Object) all



520
521
522
# File 'lib/ohm.rb', line 520

def self.all
  @all ||= Ohm::Model::Index.new(key(:all), Wrapper.wrap(self))
end

+ (Object) attr_collection_reader(name, type, model)



496
497
498
499
500
501
502
503
# File 'lib/ohm.rb', line 496

def self.attr_collection_reader(name, type, model)
  if model
    model = Wrapper.wrap(model)
    define_memoized_method(name) { Ohm::Model::const_get(type).new(key(name), model, db) }
  else
    define_memoized_method(name) { Ohm::const_get(type).new(key(name), db) }
  end
end

+ (Object) attribute(name)

Defines a string attribute for the model. This attribute will be persisted by Redis as a string. Any value stored here will be retrieved in its string representation.

Parameters:

  • (Symbol) name

    Name of the attribute.



331
332
333
334
335
336
337
338
339
340
341
# File 'lib/ohm.rb', line 331

def self.attribute(name)
  define_method(name) do
    read_local(name)
  end

  define_method(:#{name}=") do |value|
    write_local(name, value)
  end

  attributes << name unless attributes.include?(name)
end

+ (Object) attributes



524
525
526
# File 'lib/ohm.rb', line 524

def self.attributes
  @@attributes[self]
end

+ (Object) collection(name, model, reference = to_reference)

Define a collection of objects which have a reference to this model.

  class Comment < Ohm::Model
    attribute :content
    reference :post, Post
  end

  class Post < Ohm::Model
    attribute  :content
    collection :comments, Comment
    reference  :author, Person
  end

  class Person < Ohm::Model
    attribute  :name

    # When the name of the reference cannot be inferred,
    # you need to specify it in the third param.
    collection :posts, Post, :author
  end

  @person = Person.create :name => "Albert"
  @post = Post.create :content => "Interesting stuff", :author => @person
  @comment = Comment.create :content => "Indeed!", :post => @post

  @post.comments.first.content
  # => "Indeed!"

  @post.author.name
  # => "Albert"

Important: please note that even though a collection is a Set, you should not add or remove objects from this collection directly.

Parameters:

  • (Symbol) name

    Name of the collection.

  • (Constant) model

    Model where the reference is defined.

  • (Symbol) reference (defaults to: to_reference)

    Reference as defined in the associated model.

See Also:



487
488
489
490
# File 'lib/ohm.rb', line 487

def self.collection(name, model, reference = to_reference)
  model = Wrapper.wrap(model)
  define_method(name) { model.unwrap.find(:#{reference}_id" => send(:id)) }
end

+ (Object) collections



532
533
534
# File 'lib/ohm.rb', line 532

def self.collections
  @@collections[self]
end

+ (Object) connect(*options)

Makes the model connect to a different Redis instance.

Examples:

class Post < Ohm::Model
  connect :port => 6380, :db => 2

  attribute :body
end

# Since these settings are usually environment-specific,
# you may want to call this method from outside of the class
# definition:
Post.connect(:port => 6380, :db => 2)


728
729
730
# File 'lib/ohm.rb', line 728

def self.connect(*options)
  self.db = Ohm.connection(*options)
end

+ (Object) const_missing(name) (protected)



764
765
766
# File 'lib/ohm.rb', line 764

def self.const_missing(name)
  Wrapper.new(name) { const_get(name) }
end

+ (Object) counter(name)

Defines a counter attribute for the model. This attribute can’t be assigned, only incremented or decremented. It will be zero by default.

Parameters:

  • (Symbol) name

    Name of the counter.



347
348
349
350
351
352
353
# File 'lib/ohm.rb', line 347

def self.counter(name)
  define_method(name) do
    read_local(name).to_i
  end

  counters << name unless counters.include?(name)
end

+ (Object) counters



528
529
530
# File 'lib/ohm.rb', line 528

def self.counters
  @@counters[self]
end

+ (Object) create(*args)



540
541
542
543
544
# File 'lib/ohm.rb', line 540

def self.create(*args)
  model = new(*args)
  model.create
  model
end

+ (Object) define_memoized_method(name, &block)



505
506
507
508
509
510
# File 'lib/ohm.rb', line 505

def self.define_memoized_method(name, &block)
  define_method(name) do
    instance_variable_get("@#{name}") ||
      instance_variable_set("@#{name}", instance_eval(&block))
  end
end

+ (Object) encode(value)



559
560
561
# File 'lib/ohm.rb', line 559

def self.encode(value)
  Base64.encode64(value.to_s).gsub("\n", "")
end

+ (Object) find(hash)

Search across multiple indices and return the intersection of the sets.

Examples:

Finds all the user events for the supplied days

event1 = Event.create day: "2009-09-09", author: "Albert"
event2 = Event.create day: "2009-09-09", author: "Benoit"
event3 = Event.create day: "2009-09-10", author: "Albert"

assert_equal [event1], Event.find(author: "Albert", day: "2009-09-09")

Raises:

  • (ArgumentError)


554
555
556
557
# File 'lib/ohm.rb', line 554

def self.find(hash)
  raise ArgumentError, "You need to supply a hash with filters. If you want to find by ID, use #{self}[id] instead." unless hash.kind_of?(Hash)
  all.find(hash)
end

+ (Object) index(att)

Creates an index (a set) that will be used for finding instances.

If you want to find a model instance by some attribute value, then an index for that attribute must exist.

Examples:

class User < Ohm::Model
  attribute :email
  index :email
end

# Now this is possible:
User.find email: "ohm@example.com"

Parameters:

  • (Symbol) name

    Name of the attribute to be indexed.



389
390
391
# File 'lib/ohm.rb', line 389

def self.index(att)
  indices << att unless indices.include?(att)
end

+ (Object) indices



536
537
538
# File 'lib/ohm.rb', line 536

def self.indices
  @@indices[self]
end

+ (Object) list(name, model = nil)

Defines a list attribute for the model. It can be accessed only after the model instance is created.

Parameters:

  • (Symbol) name

    Name of the list.



359
360
361
362
# File 'lib/ohm.rb', line 359

def self.list(name, model = nil)
  attr_collection_reader(name, :List, model)
  collections << name unless collections.include?(name)
end

+ (Object) reference(name, model)

Define a reference to another object.

Examples:

class Comment < Ohm::Model
  attribute :content
  reference :post, Post
end

@post = Post.create :content => "Interesting stuff"

@comment = Comment.create(:content => "Indeed!", :post => @post)

@comment.post.content
# => "Interesting stuff"

@comment.post = Post.create(:content => "Wonderful stuff")

@comment.post.content
# => "Wonderful stuff"

@comment.post.update(:content => "Magnific stuff")

@comment.post.content
# => "Magnific stuff"

@comment.post = nil

@comment.post
# => nil

See Also:



424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/ohm.rb', line 424

def self.reference(name, model)
  model = Wrapper.wrap(model)

  reader = :#{name}_id"
  writer = :#{name}_id="

  attribute reader
  index reader

  define_memoized_method(name) do
    model.unwrap[send(reader)]
  end

  define_method(:#{name}=") do |value|
    instance_variable_set("@#{name}", nil)
    send(writer, value ? value.id : nil)
  end

  define_method(writer) do |value|
    instance_variable_set("@#{name}", nil)
    write_local(reader, value)
  end
end

+ (Object) set(name, model = nil)

Defines a set attribute for the model. It can be accessed only after the model instance is created. Sets are recommended when insertion and retrival order is irrelevant, and operations like union, join, and membership checks are important.

Parameters:

  • (Symbol) name

    Name of the set.



369
370
371
372
# File 'lib/ohm.rb', line 369

def self.set(name, model = nil)
  attr_collection_reader(name, :Set, model)
  collections << name unless collections.include?(name)
end

+ (Object) to_proc



516
517
518
# File 'lib/ohm.rb', line 516

def self.to_proc
  Proc.new { |id| self[id] }
end

+ (Object) to_reference



492
493
494
# File 'lib/ohm.rb', line 492

def self.to_reference
  name.to_s.match(/^(?:.*::)*(.*)$/)[1].gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.to_sym
end

Instance Method Details

- (Object) ==(other)



684
685
686
687
688
# File 'lib/ohm.rb', line 684

def ==(other)
  other.kind_of?(self.class) && other.key == key
rescue MissingID
  false
end

- (Object) attributes



668
669
670
# File 'lib/ohm.rb', line 668

def attributes
  self.class.attributes
end

- (Object) collections



676
677
678
# File 'lib/ohm.rb', line 676

def collections
  self.class.collections
end

- (Object) counters



672
673
674
# File 'lib/ohm.rb', line 672

def counters
  self.class.counters
end

- (Object) create



572
573
574
575
576
577
578
579
580
581
# File 'lib/ohm.rb', line 572

def create
  return unless valid?
  initialize_id

  mutex do
    create_model_membership
    write
    add_to_indices
  end
end

- (Object) decr(att, count = 1)

Decrement the counter denoted by :att.

Parameters:

  • (Symbol) att

    Attribute to decrement.



622
623
624
# File 'lib/ohm.rb', line 622

def decr(att, count = 1)
  incr(att, -count)
end

- (Object) delete



604
605
606
607
608
609
# File 'lib/ohm.rb', line 604

def delete
  delete_from_indices
  delete_attributes(collections) unless collections.empty?
  delete_model_membership
  self
end

- (Object) incr(att, count = 1)

Increment the counter denoted by :att.

Parameters:

  • (Symbol) att

    Attribute to increment.

Raises:

  • (ArgumentError)


614
615
616
617
# File 'lib/ohm.rb', line 614

def incr(att, count = 1)
  raise ArgumentError, "#{att.inspect} is not a counter." unless counters.include?(att)
  write_local(att, db.hincrby(key, att, count))
end

- (Object) indices



680
681
682
# File 'lib/ohm.rb', line 680

def indices
  self.class.indices
end

- (Object) inspect



699
700
701
702
703
704
705
706
707
708
709
710
711
# File 'lib/ohm.rb', line 699

def inspect
  everything = (attributes + collections + counters).map do |att|
    value = begin
              send(att)
            rescue MissingID
              nil
            end

    [att, value.inspect]
  end

  "#<#{self.class}:#{new? ? "?" : id} #{everything.map {|e| e.join("=") }.join(" ")}>"
end

- (Object) key(*args) (protected)



734
735
736
# File 'lib/ohm.rb', line 734

def key(*args)
  self.class.key(id, *args)
end

- (Object) mutex

Lock the object before executing the block, and release it once the block is done.



691
692
693
694
695
696
697
# File 'lib/ohm.rb', line 691

def mutex
  lock!
  yield
  self
ensure
  unlock!
end

- (Boolean) new?

Returns:

  • (Boolean)


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

def new?
  !@id
end

- (Object) save



583
584
585
586
587
588
589
590
591
# File 'lib/ohm.rb', line 583

def save
  return create if new?
  return unless valid?

  mutex do
    write
    update_indices
  end
end

- (Object) to_hash

Export the id and errors of the object. The `to_hash` takes the opposite approach of providing all the attributes and instead favors a white listed approach.

Examples:

person = Person.create(:name => "John Doe")
person.to_hash == { :id => '1' }
# => true

# if the person asserts presence of name, the errors will be included
person = Person.create(:name => "John Doe")
person.name = nil
person.valid?
# => false

person.to_hash == { :id => '1', :errors => [[:name, :not_present]] }
# => true

# for cases where you want to provide white listed attributes just do:

class Person < Ohm::Model
  def to_hash
    super.merge(:name => name) 
  end
end

# now we have the name when doing a to_hash
person = Person.create(:name => "John Doe")
person.to_hash == { :id => '1', :name => "John Doe" }
# => true


657
658
659
660
661
662
# File 'lib/ohm.rb', line 657

def to_hash
  attrs = {}
  attrs[:id] = id unless new?
  attrs[:errors] = errors unless errors.empty?
  attrs
end

- (Object) to_json(*args)



664
665
666
# File 'lib/ohm.rb', line 664

def to_json(*args)
  to_hash.to_json(*args) 
end

- (Object) update(attrs)



593
594
595
596
# File 'lib/ohm.rb', line 593

def update(attrs)
  update_attributes(attrs)
  save
end

- (Object) update_attributes(attrs)



598
599
600
601
602
# File 'lib/ohm.rb', line 598

def update_attributes(attrs)
  attrs.each do |key, value|
    send(:#{key}=", value)
  end
end

- (Object) write (protected)



738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
# File 'lib/ohm.rb', line 738

def write
  unless attributes.empty?
    atts = attributes.inject([]) { |ret, att|
      value = send(att).to_s
      
      ret.push(att, value)  if not value.empty?
      ret
    }

    db.multi do
      db.del(key)
      db.hmset(key, *atts.flatten)  if atts.any?
    end
  end
end

- (Object) write_remote(att, value) (protected)



754
755
756
757
758
759
760
761
762
# File 'lib/ohm.rb', line 754

def write_remote(att, value)
  write_local(att, value)

  if value.to_s.empty?
    db.hdel(key, att)
  else
    db.hset(key, att, value)
  end
end