In a plugin I am writing I am using alias to override one of the default Rails validators like so:
# Alias the original validator so it's still available under a different name
alias original_validates_uniqueness_of :validates_uniqueness_of unless method_defined?(:original_validates_uniqueness_of)
# Then alias the custom validator under the original name
alias validates_uniqueness_of :custom_validates_uniqueness_of
This all works pretty well. When "validates_uniqueness_of" is defined on an attribute in a AR model, it will use my "custom_validates_uniqueness_of" method instead. Validations are running as expected.
However, when I call:
SomeARclass.respond_to?(:validates_uniqueness_of)
..it will return false. This behavior will mess with several popular plugins.
My question:
Why is respond_to returning "false"? Is this behavior the result of alliasing? How can adjust my custom validator to make it return true?
Thank you for your help.
Erwin
Your sample, on the surface, looks like it should work. I expect you've found that, inside of the class body for class SomeARclass, you actually can call validates_uniqueness_of.
class SomeARclass
validates_uniqueness_of :first_field # works
end
However, I expect that scoped_validates_uniqueness_of is a private method. That's why you have been unable to send that message to SomeARclass from outside of its class body.
SomeARclass.validates_scoped_uniqueness_of :first_field # fails
#=> NoMethodError: private method "validates_scoped_uniqueness_of" called
for SomeARclass:Class
When you alias a private method with a new name, such as aliasing the private method validates_scoped_uniqueness_of with the new name validates_uniqueness_of, then that new method is also private. I expect that's what happened here.
The documentation for #respond_to? reads:
- (Boolean) respond_to?(symbol, include_private = false)
Returns true if obj responds to the given method. Private methods are included in the search only if the optional second parameter evaluates to true.
Related
I am working on my rails RESTful API and I have set up a versioning feature on some of the endpoints. I have a class ApiVersion which Is responsible for determining which controllers to render base on the arguments passed to it on initialization.
The class definition looks as follows:
class ApiVersion
attr_reader :version, :default
def initialize(version, default = false)
#version = version
#default = default
end
# check whether version is specified or is default
def matches?(request)
check_headers(request.headers) || default
end
private
def check_headers(headers)
# check version from Accept headers; expect custom media type 'suits'
accept = headers[:accept]
accept&.include?("application/vnd.suits.#{version}+json")
end
end
The requests work perfectly fine but when I run rubocop -A I get an error that says:
Style/OptionalBooleanParameter: Use keyword arguments when defining method with boolean argument.
def initialize(version, default = false)
I searched on the internet how to fix this type of error & got some interesting ideas which could not work in my case. For example I found one post that said I should alternate the def initialize(version, default = false) with def initialize(version, default: false) which passes the rubocop tests but then I get an internal server error with an exception: ArgumentError: wrong number of arguments (given 2, expected 1).
Does anyone have an idea on how I can fix this, or how I can alternate the class definition, to get around this issue? Thank you
First off: if you disagree with a particular rule in a linter, then turn it off. In particular, this rule is in the "Style" category, so it is not a correctness or security issue, it is a matter of style.
Secondly, boolean parameters are a code smell, since they are often Flag Parameters. A method with a flag parameter will generally do two different things depending on the value of the boolean argument, because … why else would it have the flag parameter?
However, a method that does two different things should probably be two methods.
Or, in this particular case, since it is an object initializer method specifically, that hints at the fact that there should be two classes.
Okay, with that out of the way, the nice thing about Rubocop is that it generally tells you how to fix whatever it is complaining about. In this case, it suggests using a keyword parameter. That doesn't fix the problem that the method is likely still doing two different things, but at least, it gives a name to that difference, so you can see it at the call site.
So, what Rubocop is suggest is to change the positional parameter into a keyword parameter, something like this:
def initialize(version, default: false)
Now, obviously, when you change the parameter list at the definition site, you also need to change every argument list at every call site. So, if you have a call like this (remember that #initialize gets called by ::new):
ApiVersion.new('1.2.3', true)
You need to replace it with
ApiVersion.new('1.2.3', default: true)
Throughout my application self. is not necessary to refer to a User's name. name works fine.
Why does the following code require self to work as intended?
class User< ActiveRecord::Base
before_save :validate_name
def validate_name
if self.name.nil? || self.name.empty?
self.name= "Mr. No Name"
end
end
By the way, I know that validates_presence_of can be used to prevent the save, but I want to save with a default if no name is given.
Rails 3.0.7.
Often the use of self is to force Ruby to recognize that as a method call and not mis-interpret it as a variable. Without prior knowledge of a method called day=, then day = "x" looks to Ruby like a variable assignment. self.day = "x" is always a method call.
The reason this is trouble is because the name and name= methods are added dynamically after the User class file has been parsed. The first thing Rails does when using a model is make methods for the associated database fields, but this happens after your user.rb file is parsed.
I'm working with Ruby and Rails, so any Rails extension of Ruby should be fine too.
I'm wondering if there's a way to effectively force a type on instance variables (rather, their setters and getters) that's easier than manually defining them.
The class method attr_accessor and the like don't enforce a type. I noticed for instance that Rails' ActiveRecord::Base class does automatic casting on setters. It knows from the database that a particular active record variable is an integer, and setting it with #record.integer_variable = '1' automatically casts the argument and allows further access through #record.integer_variable # => 1.
Is there a way to tap into this?
I could write my own getters and setters as class methods, but if smarter folk have already done the heavy lifting, I'd rather not have to trust myself.
I don't know if there's already something about it, but you can solve this problem with just a few lines of meta-programming:
module EnforceTypes
def attr_accessor_of_type(name, type)
send :define_method, name do
instance_variable_get("##{name}")
end
send :define_method, "#{name}=" do |v|
raise ArgumentException unless v.is_a? type
instance_variable_set("##{name}", v)
end
end
end
Usage:
class MyClass
extend EnforceTypes
attr_accessor_of_type :some_string, String
end
Of course you can make it a little smart by changing the 2nd emitted method, performing some conversions, etc.
Here's a nice reference: http://www.raulparolari.com/Ruby2/attr_accessor
And remember, almost anything that you can do by manually copy-and-pasting lots of code, can be solved with meta-programming.
I have two models,
User
Membership
The two have the following relationship with one another
user has_many :memberships
I've been trying to figure out where the build method resides, and how do i get it in a list of methods for the instance. Here is the output of the debugger that shows my delima
(rdb:63) #user.memberships.respond_to?"build"
true
While the following is returning false, shouldnt it return true??
(rdb:63) #user.memberships.instance_methods.include?"build"
false
One point is that instance_methods takes an optional boolean parameter, indicating whether you want to see methods of the instances ancestors. In your case I think you want instance_methods(true).
However, it appears that "build" is an autogenerated method, according to the documentation. Typically the autogenerated methods in ActiveRecord are implemented by overriding method_missing and handling calls to "methods" that don't actually exist. responds_to is also overridden so that the class will indicate that it responds to the correct calls. However, since those "methods" aren't actually defined, they won't show up in the instance_methods list.
Since the list of commands that a class can respond_to using method_missing is essentially infinite, I'm pretty sure there's no way to get the list. For example, an ActiveRecord model that has attributes a,b,c, and d will automatically respond to calls such as find_by_a_and_b and find_by_a_b_and_c and find_by_b_and_d and so forth, ad infinitum. There's no way to get a list of all of those possibilities.
Please note that instance_methods returns an array of String or Symbol depending on the Ruby version.
Ruby 1.8 returns an Array of String, Ruby 1.9 an Array of Symbol.
In Ruby 1.8
"".respond_to?(:upcase)
# => true
"".class.instance_methods.include?("upcase")
# => false
"".class.instance_methods.include?(:upcase)
# => false
In Ruby 1.9
"".respond_to?(:upcase)
# => true
"".class.instance_methods.include?("upcase")
# => false
"".class.instance_methods.include?(:upcase)
# => true
Also, instance_methods must be called on the class, not on the instance.
You could try:
#user = User.first
#user.methods.grep /method_name/
However, I don't think you'll see 'build' or 'create' in a list. Most likely these methods are generated dynamically
I am consuming JSON data from a third party API, doing a little bit of processing on that data and then sending the models to the client as JSON. The keys for the incoming data are not named very well. Some of them are acronyms, some just seem to be random characters. For example:
{
aikd: "some value"
lrdf: 1 // I guess this is the ID
}
I am creating a rails ActiveResource model to wrap this resource, but would not like to access these properties through model.lrdf as its not obvious what lrdf really is! Instead, I would like some way to alias these properties to another property that is named better. Something so that I can say model.id = 1 and have that automatically set lrdf to 1 or puts model.id and have that automatically return 1. Also, when I call model.to_json to send the model to the client, I dont want my javascript to have to understand these odd naming conventions.
I tried
alias id lrdf
but that gave me an error saying method lrdf did not exist.
The other option is to just wrap the properties:
def id
lrdf
end
This works, but when I call model.to_json, I see lrdf as the keys again.
Has anyone done anything like this before? What do you recommend?
Have you tried with some before_save magic? Maybe you could define attr_accessible :ldrf, and then, in your before_save filter, assign ldrf to your id field. Haven't tried it, but I think it should works.
attr_accessible :ldrf
before_save :map_attributes
protected
def map_attributes
{:ldrf=>:id}.each do |key, value|
self.send("#{value}=", self.send(key))
end
end
Let me know!
You could try creating a formatter module based on ActiveResource::Formats::JsonFormat and override decode(). If you had to update the data, you'd have to override encode() also. Look at your local gems/activeresource-N.N.N/lib/active_resource/formats/json_format.rb to see what the original json formatter does.
If your model's name is Model and your formatter is CleanupFormatter, just do Model.format = CleanupFormatter.
module CleanupFormatter
include ::ActiveResource::Formats::JsonFormat
extend self
# Set a constant for the mapping.
# I'm pretty sure these should be strings. If not, try symbols.
MAP = [['lrdf', 'id']]
def decode(json)
orig_hash = super
new_hash = {}
MAP.each {|old_name, new_name| new_hash[new_name] = orig_hash.delete(old_name) }
# Comment the next line if you don't want to carry over fields missing from MAP
new_hash.merge!(orig_hash)
new_hash
end
end
This doesn't involve aliasing as you asked, but I think it helps to isolate the gibberish names from your model, which would never have to know those original names existed. And "to_json" will display the readable names.