Walt Jones
Walt Jones

Reputation: 1328

How to iterate ActiveRecord Attributes, including attr_accessor methods

I've looked everywhere for an elegant solution. The essential problem seems to be that ActiveRecord attributes that map to database columns are handled completely differently in ActiveRecord::Base than attr_accessor methods.

I would like to do something like:

model.attribute_names.each do |name|
  # do stuff
end

in a way that also includes attr_accessor fields, but not any other instance methods. I know this in not built-in, but what is the most elegant way to do it?

Upvotes: 7

Views: 13231

Answers (3)

MTarantini
MTarantini

Reputation: 999

The solution I came up with for myself builds upon Orion Edwards' answer.

The code:

klass_attributes = klass.column_names + klass.instance_methods(false). map(&:to_s).keep_if{|a| a.include?('=')}.map{|a| a.sub('=', '')}

The breakdown:

klass.instance_methods(false) brings back only instance methods, and not inherited methods.

map(&:to_s) converts the array of symbols into an array of strings so we can use the include? method. Also needed to merge with array of strings returned by column_names.

keep_if{|a| a.include?('=')} will remove all strings within the array that do not have an equals sign. This was important, since I noticed that attr_accessor attributes all had '='. Ex: 'app_url' vs 'login_url='

map{|a| a.sub('=', '')} is the final piece that then removes the '=' from each string in the array.

You end up with an array of strings that represent attributes, including attr_accessor attributes.

Upvotes: 0

user1103596
user1103596

Reputation:

I came here looking to do the same thing, and found out it was the wrong approach altogether thanks to Orion's answer.

Incase anyone else's use case is similar to mine, here's my solution. I was using attr_accessor to add extra properties to the models after querying them from ActiveRecord. I then wanted to output the results as JSON etc.

A better solution is to first convert the Models from ActiveRecord into regular hashes, and then add the attr_accessor properties as regular hash keys.

Example:

model_hash = model_from_activerecord.attributes.to_options
model_hash[:key] = value

Upvotes: 3

Orion Edwards
Orion Edwards

Reputation: 123652

You can't really solve this. You can approximate a hack, but it's not something that will ever work nicely.

model.attribute_names should get you all the ActiveRecord ones, but the attr_accessor fields are not fields. They are just ordinary ruby methods, and the only way to get them is with model.instance_methods.

Idea 1

You could do model.attribute_names + model.instance_methods, but then you'd have to filter out all your other normal ruby methods initialize, save, etc which would be impractical.

To help filter the instance_methods you could match them up against model.instance_variables (you'd have to account for the @ sign in the instance variables manually), but the problem with this is that instance variables don't actually exist at all until they are first assigned.

Idea 2

In your environment.rb, before anything else ever gets loaded, define your own self.attr_accessor in ActiveRecord::Base. This could then wrap the underlying attr_accessor but also save the attribute names to a private list. Then you'd be able to pull out of this list later on. However I'd advise against this... monkey-patching core language facilities like attr_accessor is guaranteed to bring you a lot of pain.

Idea 3

Define your own custom_attr_accessor in ActiveRecord::Base, which does the same thing as Idea 2, and use it in your code where you want to be able to retrieve the attribute names. This would be safe as you won't be clobbering the built-in attr_accessor method any more, but you'll have to change all your code to use custom_attr_accessor where neccessary

I guess in summary, what are you trying to do that needs to know about all the attr_accessor fields? Try look at your problem from a different angle if you can.

Upvotes: 10

Related Questions