Why alias_method fails in Rails model - ruby-on-rails

class Country < ActiveRecord::Base
#alias_method :name, :langEN # here fails
#alias_method :name=, :langEN=
#attr_accessible :name
def name; langEN end # here works
end
In first call alias_method fails with:
NameError: undefined method `langEN' for class `Country'
I mean it fails when I do for example Country.first.
But in console I can call Country.first.langEN successfully, and see that second call also works.
What am I missing?

ActiveRecord uses method_missing (AFAIK via ActiveModel::AttributeMethods#method_missing) to create attribute accessor and mutator methods the first time they're called. That means that there is no langEN method when you call alias_method and alias_method :name, :langEN fails with your "undefined method" error. Doing the aliasing explicitly:
def name
langEN
end
works because the langEN method will be created (by method_missing) the first time you try to call it.
Rails offers alias_attribute:
alias_attribute(new_name, old_name)
Allows you to make aliases for attributes, which includes getter, setter, and query methods.
which you can use instead:
alias_attribute :name, :langEN
The built-in method_missing will know about aliases registered with alias_attribute and will set up the appropriate aliases as needed.

Related

Setters from Rails4 InstanceMethods for non-existing fields

I am writing a gem rails4 where i can add dynamic attributes which wont persist in the actual model table, rather it will save in my second table with a reference.
Now i have before_validation to an instance method, where i am trying to set the default value to dynamically added attribute.
I am getting the error as
can't write unknown attribute
The code is
self[attr_name.to_sym]=attr_type[:default_value]
Kindly advice on this, how to do setters from the instance method for the non-existing fields.
I have used instance_variable_set for the same in ClassMethods.
You can use attr_accessor method for this purpose in your model.
class Sample << ActiveRecord::Base
attr_accessor :attr_name
#setter method
def attr_name=(val)
self[:attr_name] = val
end
#getter method
def attr_name
self[:attr_name]
end
end
Now, you can call any where instance method like following.
self[attr_name.to_sym]=attr_type[:default_value]

How do you define a class method?

I have a model OutcomeData with controller OutcomeDatas.
In OutcomeData, I have a method as_cleaned_hash that right now, doesn't do a damn thing. Let's just pretend it returns 'hello'
class OutcomeData < ActiveRecord::Base
attr_accessible :key, :outcome_uid, :task_id, :value
belongs_to :task
belongs_to :outcome
def as_cleaned_hash
'hello i am alive'
end
This is the method that as_cleaned_hash is supposed to follow, if it matters:
#outcome_data = OutcomeData.find_all_by_outcome_uid(params[:outcome_uid])
hash = Hash.new
#outcome_data.each do |p|
unless p[:value].blank? || p[:key] == 'raw'
hash[p[:key]] = p[:value]
end
end
This works fine -- right now I'm throwing it into my controller actions, but since it needs to be used throughout my app, I can't let this happen.
So, for whatever reason, I get an undefined method error.
I called OutcomeData.methods to see if the method was even there, and, nope. (see list here: http://pastebin.com/B3y1r2w7)
OutcomeData.respond_to?('as_cleaned_hash') returns false.
There's nothing fancy going on either, so I'm not quite sure what's happening.
Rails 3.2.12 with Ruby 2.0.0-p195
To define a class method, the syntax is
def self.foo
end
You have defined an instance method.

Redefine a single method on an instance to call superclass method

We have two Rails models: Person and Administrator. We're disallowing removal of Administrators at the model level:
class Person < ActiveRecord::Base
end
class Administrator < Person
def destroy
raise "Can't remove administrators."
end
end
me = Administrator.new
me.destroy # raises an exception
I'd like to be able to get around this during testing, but only for specific instances created during setup and teardown. I don't want to change the behavior of the class, so class_eval and remove_method aren't feasible.
I tried to redefine the actual instance's #destroy method:
def me.destroy
super
end
or redefine it on the singleton class:
class << me
def destroy
super
end
end
but those still raised the exception. I couldn't figure out how to get it to call the superclass method implicitly. I ended up creating my own destroy! method (since that's not actually a method in ActiveRecord), which sort of violates my desire not to change the behavior of the class:
def destroy!
ActiveRecord::Persistence.instance_method(:destroy).bind(self).call
end
Is there any simple way to tell a single instance method to call its superclass method?
Final Answer: Based on the article Holger Just linked to, I was able to simply call the superclass method explicitly:
def me.destroy
self.class.superclass.instance_method(:destroy).bind(self).call
end
I'd try to refactor the behavior to be more test-friendly. E.g. you could allow an optional parameter to destroy e.g. i_know_what_im_doing that has to be set to true to carry out the destroy. Alternatively you could cancel the destroy with a before_destroy hook like
class Administrator < Person
def before_destroy(record)
# You can't destroy me
false
end
end
In your tests, you can then call Administrator.skip_callback :before_destroy to ignore it and to have a proper destroy.
Finally, you could overwrite / stub the method in your tests. While you say you don't want to modify the class's behavior, you still have to do that (and implicitly do that with your destroy! method today).
I'm not familiar with Ruby metaprograming, so I wont answer if you can a call a method of the super class on an instance without modifying it. But you can create a hook to a superclass method with alias :
class Administrator < Person
alias :force_destroy :destroy
def destroy
raise "Can't remove administrators."
end
end
With this, admin.destroy will raise an exception, but admin.force_destroy will actually call the ActiveRecord destroy.

Paginate throws error for Custom Validations

I'm attempting to create a custom validation for one of my models in Rails 2.3.5, but I keep recieving the following error everytime I run my testing suite:
`method_missing_without_paginate': undefined local variable or method `validates_progression'
app/models/project.rb
class Project < ActiveRecord::Base
...
validates_progression
def validates_progression
true # stubtastic!
end
end
I can't seem to make much of this~
It doesn't work because you are defining a method with instance scope and you are trying to call it within the class scope. You have two alternatives:
Instance Scope
class Project < ActiveRecord::Base
validate :validates_progression
def validates_progression
true # stub
end
end
Class scope
class Project < ActiveRecord::Base
def self.validates_progression
true # stub
end
# Be sure to define this method before calling it
validates_progression
end
The second alternative doesn't really makes sense unless you want to wrap an other filter.
class Project < ActiveRecord::Base
def self.validates_progression
validates_presence_of :progression
validates_length_of ...
end
# Be sure to define this method before calling it
validates_progression
end
Otherwise, go with the first solution.
The pagination reference is a red herring. The clue is the 'without'. The will paginate gem has aliased the existing method_missing method and called it method_missing_without_pagination. So, the problem is a standard missing method error.
The method is missing because it is a) not defined when you call it and b) not in the correct scope (you are trying to call an instance method in the scope of the class).
You can add your custom validation by using validate with the symbol for your validation method:
validate :validates_progression
def validates_progression
true
end

alias_attribute and creating and method with the original attribute name causes a loop

Im trying to dynamically create a method chain in one attribute in my model.
By now I have this function:
def create_filtered_attribute(attribute_name)
alias_attribute "#{attribute_name}_without_filter", attribute_name
define_method "#{attribute_name}" do
filter_words(self.send("#{attribute_name}_without_filter"))
end
end
so I receive a string with the attribute name, alias it for '_without_filter' (alias_method or alias_method_chain fails here, because the attribute isnt there when the class is created),
and I create a new method with the attribute name, where I filter its contents.
But somehow, when I call "#{attribute_name}_without_filter" it calls my new method (i think because the alias_attribute some how), and the program goes into a stack loop.
Im trying to rename that attribute, so I can use its name for a method...
Can someone please enlighten me on this.
There is a difference between alias_method and alias_attribute. alias_method actually makes a copy of the old method, whereas alias_attribute just defines new methods, which call old ones.
Note, that model.attribute and model.attribute= methods in ActiveRecord simply call read_attribute and write_attribute, so you always can access your attribute, even if you override it's getter or setter:
define_method "#{attribute_name}" do
filter_words(self.read_attribute(attribute_name))
end

Resources