Override a Mongoid model's setters and getters - ruby

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?

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

def name=(projectname)
self[:name] = projectname.capitalize
end

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

Related

How to access ActiveModel attributes from a method call on the class?

I need to reference user#role to define an association in a module. I've tried with a block as shown below, but that doesn't work. How does Rails implement behavior like this?
class User < ActiveRecord::Base
include Profile
has_profile { |user| { class_name: "#{user.role}::Profile" }}
end
module Profile
extend ActiveSupport::Concern
module ClassMethods
def has_profile(&block)
role = ### How to access #role ? ###
class_eval do
has_one :profile, class_name: "#{role}::Profile"
end
...
You might need to do something like this. I didn't tested and I'm just supposing you can do this kind of stuff
class User < ActiveRecord::Base
include Profile
has_profile { |user| user.role }
end
module Profile
extend ActiveSupport::Concern
included do
after_initialize :_init_profile
end
def _init_profile
role = #_role_block.call(self)
# Here we do class eval on singleton so we dont change base class
# I'm not sure if this works as it is but should be close enought
class << self; self; end.class_eval do
has_one :profile, class_name: "#{role}::Profile"
end
end
module ClassMethods
def has_profile(&block)
#_role_block = block
...
This works:
module Models
module Profile
extend ActiveSupport::Concern
included do
after_initialize :_init_profile
end
module ClassMethods
def has_profile(&block)
#profile_association_block = block
end
end
def _init_profile
block = self.class.instance_variable_get :#profile_association_block
self.class.has_one :profile, block.call(self)
end

Can't mass-assign protected attributes attr_accessor and attr_accessible

in rails 2.3.11, I have below in model
attr_accessor :person_id
and in controller
#project.person_id = current_user.id
now, I am converting this in rails 3.2.11 and I am getting
Can't mass-assign protected attributes: person_id
so I changed in model, I removed :person_id from attr_accessor and add below line
attr_accessible :person_id
but I am uisng person_id in controller, here it is
#project.person_id = current_user.id
I am getting this now
NoMethodError in ProjectsController#create
undefined method `person_id=' for #<Project:0x19cc51a>
any idea or help, How can I fix this? How can I handle both attr_accessor & attr_accessible?
attr_accessor :person_id and attr_accessible :person_id are not the same.
attr_accessor is the Ruby method. In short its shortcut for methods:
def person_id
#person_id
end
def person_id=(value)
#person_id = value
end
attr_accessible is the Rails method. Which gets list of attributes allowed to be mass-assigned. You can read about here.
Thus in your case you need both of them.
attr_accessor :person_id
attr_accessible :person_id

How do I define associated Factories with FactoryBot?

Given two models, Alert and Zipcode, where one Alert must have 1 or more Zipcodes:
class Alert < ActiveRecord::Base
attr_accessible :descr, :zipcode
has_many :zipcode
validates :zipcode, :length => { :minimum => 1 }
end
class Zipcode < ActiveRecord::Base
attr_accessible :zip
belongs_to :alert
end
How do I write my FactoryBot factories so that:
Zipcode factories are defined in their own file
Alert factories are defined in their own file
Alert can rely on the factory defined by Zipcode?
All of the documentation and examples I read expect you to define the contained class inside the parent factory file, blob them all together, or make some other compromise or work-around. Isn't there a clean way to keep the spec factories separate?
The trick is to make sure the container class, that is, the one with a has_many statement in its definition, creates the contained class as an array in FactoryBot. For example:
In your spec/factories/zipcodes.rb:
FactoryBot.define do
factory :zipcode do
zip { 78701 + rand(99) }
end
end
And in spec/factories/alerts.rb:
FactoryBot.define do
factory :alert do
zipcode { Array.new(3) { FactoryBot.build(:zipcode) } }
end
end

If I can call SomeClass with some_class, then how can I call Module::SomeClass?

Post is accessible via
Blog.first.posts
How do I do this with a module name space? Such as Engine::Post
Blog.first.???
Thanks
Try explicitly giving Mongoid the class name
class Engine::Blog
include Mongoid::Document
embeds_many :posts, :class_name => "Engine::Post"
end

Rails Model callback: Passing field as parameter to callback classes?

I want to implement before_validaton callback in a separate class so that it can be reused by multiple model classes.
Here in callback i want to strip field passed as parameter but i am not sure how to pass parameter to callback class. Also i want to pass this as reference rather than by value(not sure if this concept is in Ruby Rails). I am following the link http://guides.rubyonrails.org/active_record_validations_callbacks.html#callback-classes
Here is code which is not completely correct, please help for same
class StripFieldsCallback
def self.before_validation(field)
field = field.strip
end
end
class User < ActiveRecord::Base
validates_uniqueness_of :name, :case_sensitive => false
validates_length_of :name, :maximum => 50
before__validation StripFieldsCallback(name)
end
If i define method in model in itself rather than defining in separate callback class code is like this (which works fine)
class User < ActiveRecord::Base
validates_uniqueness_of :name, :case_sensitive => false
validates_length_of :name, :maximum => 50
before__validation :strip_blanks
protected
def strip_blanks
self.name = self.name.strip
end
end
Of course it is not good to replicate methods in all of models so i want to define method in callback classes.
You may do this or use normalize_attributes gem
module StripFieldsCallback
def before_validation_z(field)
write_attribute(field, read_attribute(field).strip) if read_attribute(field)
end
end
class User < ActiveRecord::Base
include StripFieldsCallback
before_validation lambda{|data| data.before_validation_z(:name)}
end

Resources