rails class instead of switch - ruby-on-rails

I have been looking for over an hour on Internet and I can't find anything about this.
I am creating filters for data of a website and currently these are handled by a case statement
class MyClass
attr_accessor :attribute
def self.function(value)
query = case value
when "open" then "Open"
...
end
where(:attribute => query)
end
end
Because of various reasons (i.e. dynamic instead of hard coding the filters) I want to create a model out of this with a getter and setter, but I can't get this to work
My new function:
def self.function(value)
Attribute.name = value
where(:attribute => Attribute.name)
end
My new model:
class Attribute
attr_accessor :name
end
And the test:
it "should set the attribute to 'hello'" do
MyClass.function("hello")
Attribute.name.should eql "hello"
end
gives an error:
Failure/Error: Myclass.function("hallo")
NoMethodError:
undefined method `name=' for Attribute:Class
Any help would be appreciated

This is because the attr_accessor is defining instance method (ie: method that works on an instance of Attribute) and you try to use it as class method (ie: Attribute.name).
You may rewrite your function this way :
def self.function(value)
attribute = Attribute.new
attribute.name = value
where(:attribute => attribute.name)
end

Related

How to apply attr_accessor for a variable in hash in Rails 3

I have some variables in a instance variable (for other methods can access the variable) which type is hash.
if I don't want all members in hash #iw2 applied attr_accessor
only #iw2[:dir] can be modified by others.
#iw2 ={}
#iw2[:dir] = "#{Rails.root}/#{ENV["module_handy_network_tools_src_path"]}"
#iw2[:prog_path] ="#{#iw2[:dir]}/#{ENV["module_handy_network_tools_prog_path"]}"
So I wrote that way,
attr_accessor :iw2[:dir]
But I got the error
TypeError (can't convert Symbol into Integer):
app/helpers/handy_network_tools_helper.rb:8:in `[]'
How to fix the problem, thanks in advance.
[2] pry(#<HandyNetworkToolsController>)> #iw2.class
=> Hash
Edit
When you find yourself having many methods with the same prefix (iw2 in this case), it is a sign that there's a hidden object in there. How about this? Better?
class Iw2
def initialize(hash)
#dir = hash[:dir]
#prog_path = hash[:prog_path]
end
attr_accessor :dir, :prog_path
end
class MyClass
def initialize
#iw2 = Iw2.new(:dir => "a rails path",
:prog_path => "some another rails path")
end
delegate :dir, :prog_path, :to => :#iw2
end
mc = MyClass.new
mc.dir # => "a rails path"
mc.prog_path # => "some another rails path"
Original answer
Well, attr_accessor doesn't work like that. You can always use old-fashioned getters/setters.
def iw2_dir
#iw2[:dir]
end
def iw2_dir=(dir)
#iw2[:dir] = dir
end
You can then implement your own attr_sub_accessor that will generate such methods for you.
attr_sub_accessor :iw2, :dir
attr_sub_accessor :iw2, :prog_path
(I think explicit getters/setters are better in this case)

rails find_by_looking_in_every_field ...or... "why is my function missing?"

This is a two parter. I'd be happy with either of the approaches below or other suggestions.
I'd like to be able to retrieve records/objects using my model by passing it a search term and having it look for that search term in any field in the model, or any field that the model deems viable. So, as an example:
class Product < ActiveRecord::Base
...
def search_all_fields(search_term)
return search_term.length == 0 ? nil : Product.where("serial_number like :find_me", { :find_me => search_term })
end
end
This is from a Product model. The same function in the Company model might look like:
class Company < ActiveRecord::Base
...
def search_all_fields(search_term)
return search_term.length == 0 ? nil : Company.where("customer_number like :find_me or name like :find_me", { :find_me => search_term })
end
end
I would love a "railsy" way to do this, such as "find_by_looking_everywhere" but I haven't been able to find such a thing. I've found lots of suggestions for searching a single field for multiple values, but not searching multiple fields for a single value. So that's "Part 1," is there a "railsy" way to do this?
"Part 2" ... using the code above, why am I getting the following exception?
undefined method `search_all_fields` for #<Class:0xa38f2ac>
I'm calling the methods using #products = Product.search_all_fields("xy3445") or #companies = Company.search_all_fields("high")?? The trace shows that the exception is being raised by just a generic class. It doesn't say #<Product...> or #<Company...>
I'm a little lost... any and all help appreciated.
Thanks, gang.
Your method is an instance method (the Model need to be instanciated to access this method). You need a Class method (means you don't need an instance of Company to call it, like the methods where(), find() etc).
class Company < ActiveRecord::Base
def say_hello
return "Hello world!"
end
end
This method say_hello can only be called from an instance of Company (instance method):
company = Company.first
company.say_hello #=> "Hello world!"
# but this will raise a NoMethodError:
Company.say_hello #=> NoMethodError
In order to define a method as a class method, you can do the following:
class Company < ActiveRecord::Base
def self.say_hello
return "Hello world!"
end
# OR you can use the name of the model instead of the self keyword:
def Company.say_hello
return "HEllo World!"
end
end
Now you can do:
Company.say_hello
#=> "HEllo World!"
# but this will fail:
Company.first.say_hello
#=> NoMethodError

Rails delegated attribute not in #attributes, read_attribute returns nil

I have a model which delegates some methods and attributes to a different model, let's say
class ModelOne < ActiveRecord::Base
# this model has some_property column in database
end
and
class ModelTwo < ActiveRecord::Base
belongs_to :model_one
delegate :some_property, :to => :model_one
end
The problem is that I can access 'some_property' by calling the method but not through read_attribute.
> obj1 = ModelTwo.last
> obj1.some_property
=> "some value"
> obj1.read_attribute :some_property
=> nil
> obj1.inspect
=> "#ModelTwo ... , ... , some_property: nil "
It is possible to set this attribute:
> obj1.some_property = "some value"
> obj1.inspect
=> "#ModelTwo ... , ... , some_property: "some value" "
So I can access delegated attribute by calling it but not by read_attribute or through inspect. Is there any chance to get the attribute value by read_attribute?
Maybe you should try to override read_attribute method. I am not using read_attribute, but in a similar scenario I had to override the hash method:
def [](key)
value = super
return value if value
if super(key+"_id")
begin
send(key)
rescue NoMethodError
end
end
end
Its not pretty and there are possibly security issues with calling send(key) without more accurate validation.
If you look into the implementation of read_attribute:
# File activerecord/lib/active_record/attribute_methods/read.rb, line 128
def read_attribute(attr_name)
self.class.type_cast_attribute(attr_name, #attributes, #attributes_cache)
end
is not based on the attribute accessor (in your case some_property) but is accessing the #attributes instance variable directly, which makes sense because read_attribute is a lower level api that allows you to bypass the accessor. Thus, you can't do what you're trying.
This may not be the answer you're looking for, but what I'd be reconsidering in your design is why you need to access your attribute via read_attribute. If you show us where and how you're using read_attribute, I'll be happy to try and help you farther.

Rails Validation Error

While trying to add an error message using add_to_base, I am getting an undefined method 'errors' message. I am defining it in my model. Am I supposed to include any other file in order to access the errors variable.
Model File - I am defining it inside a method
self.errors.add_to_base("Invalid Name")
Error Message
undefined method `errors' for #<Class:0x0000010179d7a0>
I tried by calling it as errors.add_to_base("Invalid Name") also but still getting the same error.
Thanks.
you should call it in your callback method, something like following
def validate
if !self.interests.blank? && !self.interests.match("<").nil?
self.errors.add :base, 'Please ensure that Interest field do not contain HTML(< and >) tags'
end
end
I suspect that you have defined your method as a class method, instead of as an instance method.
Class methods look like this on ruby:
def self.checkFoo()
...
end
Instance methods looks like this:
def checkFoo()
...
end
Check that your checkFoo method is an instance method, and then use it like this:
class MyModel < ActiveRecord::Base
validate :foo
private
def checkFoo()
self.errors.add etc..
end
end
Typically used the validation callbacks, model errors are used both to cause the prospective database save to fail and to set up a contextual error messages for the end-user. The add_to_base variant is intended for general, non-specific error conditions (i.e. not associated with a particular model attribute).
class MyModel < ActiveRecord::Base
validate do |my_model|
if my_model.some_attribute.blank? # For example
my_model.errors.add :my_model, "must be filled in"
end
end
end
Subsequently
#my_model = MyModel.create(:some_attribute => "")
would fail and the #my_model.errors.full_messages array would contain
[ ..., "Some_attribute must be filled in", ... ]
There is however a shorthand for the above example as follows
class MyModel < ActiveRecord::Base
validates_presence_of :some_attribute, :msg => "must be filled in"
end
Looks like your 'self.errors.add_to_base("Invalid Name")' doesn't have any problem
But your model should inherit from ActiveRecord::Base
cheers
sameera

Is there a way to validate a specific attribute on an ActiveRecord without instantiating an object first?

For example, if I have a user model and I need to validate login only (which can happen when validating a form via ajax), it would be great if I use the same model validations defined in the User model without actually instantiating a User instance.
So in the controller I'd be able to write code like
User.valid_attribute?(:login, "login value")
Is there anyway I can do this?
Since validations operate on instances (and they use the errors attribute of an instance as a container for error messages), you can't use them without having the object instantiated. Having said that, you can hide this needed behaviour into a class method:
class User < ActiveRecord::Base
def self.valid_attribute?(attr, value)
mock = self.new(attr => value)
unless mock.valid?
return mock.errors.has_key?(attr)
end
true
end
end
Now, you can call
User.valid_attribute?(:login, "login value")
just as you intended.
(Ideally, you'd include that class method directly into the ActiveRecord::Base so it would be available to every model.)
Thank you Milan for your suggestion. Inspired by it I created a simple module one can use to add this functionality to any class. Note that the original Milans suggestion has a logic error as line:
return mock.errors.has_key?(attr)
should clearly be:
return (not mock.errors.has_key?(attr))
I've tested my solution and it should work, but ofc I give no guarantees. And here's my glorious solution. Basically a 2-liner if you take away the module stuff.. It accepts method names as stings or symbols.
module SingleAttributeValidation
def self.included(klass)
klass.extend(ClassMethods)
end
module ClassMethods
def valid_attribute?(attr, value)
mock = self.new(attr => value)
(not mock.valid?) && (not mock.errors.has_key?(attr.class == Symbol ? attr : attr.to_sym))
end
end
end
To use your standard validation routines:
User.new(:login => 'login_value').valid?
If that does not work for you, build a custom class method for this:
class User < ActiveRecord::Base
validate do |user|
user.errors.add('existing') unless User.valid_login?(user.login)
end
def self.valid_login?(login)
# your validation here
!User.exist?(:login=> login)
end
end
I had a hell of a time getting this to work in Rails 3.1. This finally worked. (Not sure if it's the best way to do it, I'm kind of a newb.). The problem I was having was that value was being set to type ActiveSupport::SafeBuffer, and was failing validation.
def self.valid_attribute?(attr, value)
mock = User.new(attr => "#{value}") # Rails3 SafeBuffer messes up validation
unless mock.valid?
return (not mock.errors.messages.has_key?(attr))
end
return true
end
I have gone with the custom class solution but I just wanted to make sure there was no better way
class ModelValidator
def self.validate_atrribute(klass, attribute, value)
obj = Klass.new
obj.send("#{attribute}=", value)
obj.valid?
errors = obj.errors.on(attribute).to_a
return (errors.length > 0), errors
end
end
and I can use it like
valid, errors = ModelValidator.validate_attribute(User, "login", "humanzz")
class User < ActiveRecord::Base
validates_each :login do |record, attr, value|
record.errors.add attr, 'error message here' unless User.valid_login?(value)
end
def self.valid_login?(login)
# do validation
end
end
Just call User.valid_login?(login) to see if login itself is valid
An implementation of the 'valid_attribute' method you are suggesting:
class ActiveRecord:Base
def self.valid_attribute?(attribute, value)
instance = new
instance[attribute] = value
instance.valid?
list_of_errors = instance.errors.instance_variable_get('#errors')[attribute]
list_of_errors && list_of_errors.size == 0
end
end
How about:
User.columns_hash.has_key?('login')

Resources