13

Is there a way to override a setter or getter for a model in Mongoid? Something like:

class Project
  include Mongoid::Document
  field :name, :type => String
  field :num_users, type: Integer, default: 0
  key :name
  has_and_belongs_to_many :users, class_name: "User", inverse_of: :projects

  # This will not work
  def name=(projectname)
    @name = projectname.capitalize
  end
end

where the name method can be overwritten without using virtual fields?

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
GTDev
  • 5,488
  • 9
  • 49
  • 84
  • related: http://stackoverflow.com/questions/6699503/mongoid-custom-setters-getters-and-super – marcgg Sep 05 '11 at 10:13

3 Answers3

24

better use

def name=(projectname)
  super(projectname.capitalize)
end

the method

self[:name] = projectname.capitalize

can be dangerous, cause overloading with it can cause endless recursion

sandrew
  • 3,109
  • 5
  • 19
  • 29
  • @GearHead I have also switched to super over timme(it looks better), although still parts of my code use self[:name] notation and haven't faced any recursions till now. – rubish Aug 29 '12 at 21:07
  • 2
    How could `super` work if there is no super class? `Mongoid::Document` is included as Module, I'm confused here ... – tothemario Feb 19 '13 at 23:21
  • @tothemario look at the http://blog.rubybestpractices.com/posts/gregory/030-issue-1-method-lookup.html and http://blog.rubybestpractices.com/posts/gregory/031-issue-2-method-lookup.html – sandrew Feb 21 '13 at 02:49
  • @tothemario If a module is included in a class then it acts as a super class. – don_Bigote Aug 30 '22 at 19:03
18
def name=(projectname)
  self[:name] = projectname.capitalize
end
rubish
  • 10,887
  • 3
  • 43
  • 57
  • 1
    @user923636 you cannot change the "_id" field of a document once created. So if project name gets changed, you will have to delete old document and create a new one with changed name. – rubish Sep 05 '11 at 14:23
2

I had a similar issue with needing to override the "user" setter for a belongs_to :user relationship. I came up with this solution for not only this case but for wrapping any method already defined within the same class.

class Class  
  def wrap_method(name, &block)
    existing = self.instance_method(name)

    define_method name do |*args|
      instance_exec(*args, existing ? existing.bind(self) : nil, &block)
    end
end

This allows you to do the following in your model class:

wrap_method :user= do |value, wrapped|
    wrapped.call(value)
    #additional logic here
end
Jake Hoffner
  • 1,037
  • 9
  • 17
  • Do not try to reinvent the bicycle. Use alias_method_chain for this. – sandrew Dec 19 '13 at 07:44
  • thanks for this! Having trouble with relationship setters and getters as well. sandrew's comment had me look into a_m_c's and I learned about Ruby 2.0's Module#prepend. Great solution and really clean - http://dev.af83.com/2012/10/19/ruby-2-0-module-prepend.html – Hunter Barrington Jun 15 '14 at 06:02