5

I have a Person model, which includes a property representing the data of birth (birth_date).

I also have a method called age(), which works out the current age of the person.

I now have need to run queries based on the person's age, so I have replicated the logic of age() as a computed column in MySQL.

I cannot workout how I would make this additional column part of the default select statement of the model.

I would like to be able to access the age as if it were a native property of the Person model, to perform queries against it and access the value in my views.

Is this possible, or am barking up the wrong tree?

I thought I might be able to define additional fields through default_scope or scope, but these methods seem to only recognise existing fields. I also tried default_scope in tandem with attr_assessor.

Possible workarounds I've considered but would prefer not to do:

  • Create an actual property called age and populate through the use of callbacks. The date is always changing, so this obviously would be be reliable.

  • Replicate the logic in ActiveRecord as age() and in a scope as a where cause. This would achieve what I need, but doesn't feel very DRY.

  • I am already caching the results of the age() method. it is the ability to use the field in where clauses that I am most interested in.

There must be a way to define dynamic fields through SQL that I can access through the model by default.

Any help would be appreciated.

Rich

UPDATE

An example of my failed attempt to utilise scopes:

default_scope :select => "*, 2 as age"

attr_accessor :age

age is blank, I assume because scopes only deal with limiting, not extending.

kim3er
  • 6,306
  • 4
  • 41
  • 69
  • 1
    Please show some code. have you set some aliases for additional attributes, like `select('expression AS alias')` ? – taro Jul 21 '11 at 08:40
  • @taro, I've added an example. – kim3er Jul 21 '11 at 09:12
  • I think, attr_accessor declaration is not needed. – taro Jul 21 '11 at 09:42
  • @taro, no but I don't think it's the problem either. – kim3er Jul 21 '11 at 09:57
  • have you tried to remove that declaration? – taro Jul 21 '11 at 09:58
  • @taro: Dude, form that as answer, you deserve every bit of that bounty. I cannot believe I didn't try that. Thank you so much. – kim3er Jul 21 '11 at 10:06
  • Have you considered to use a stored function in MySQL to calculate the age in your query? You then can add a scope like `:select => "age(birth_date) > 18"` or use lamda function enabled scope to have variable ages. – arnep Jul 21 '11 at 10:16
  • Thanks @arnep, yes I have. But what do you mean by "lamda function enabled scope to have variable ages", you've peaked my interest. – kim3er Jul 21 '11 at 10:22
  • 1
    Great, but I don't care about the prize :) – taro Jul 21 '11 at 10:41
  • Thanks again @taro. I would like to have the answer clearly defined. I can answer it myself, but I would prefer credit to go where it is due. I will not submit my answer for a couple of days, in case you change your mind. – kim3er Jul 21 '11 at 10:45
  • 1
    @kim3er: you can read about lambda function in named scopes in the [Rails API](http://api.rubyonrails.org/classes/ActiveRecord/NamedScope/ClassMethods.html). For your problem it would be: `scope :age, lambda {|age| :conditions => ["age(birch_date) > ?", age] }`. Or just use one of the [many possibilities](http://www.google.de/#q=mysql+age+from+date+of+birth) to calculate the actual age in this scope. – arnep Jul 21 '11 at 11:51

2 Answers2

4

kim3er your solution to your problem is simple. Follow these steps:

Loose the attr_accessor :age from your model. You simply don't need it.

Leave the default scope at the Person model: default_scope :select => "*, 2 as age"

Lastly open up a console and try

p = Person.first
p.age
=> 2

When you define a select using as, Rails will automagically add those methods on the instances for you! So the answer to your question:

There must be a way to define dynamic fields through SQL that I can access through the model by default.

is:

Rails

Gerry
  • 5,326
  • 1
  • 23
  • 33
  • Thanks Gerry. @taro had already answered my question in the comments above, but I appreciate the extra context you've provided, so I'll mark the question as answered. – kim3er Jul 27 '11 at 08:15
1

I'm not an expert by any stretch, but it seems you want:

class Person < ActiveRecord::Base

scope :exact_age, lambda { |a| where('age = ?', a) }
scope :age_gt, lambda { |a| where('age > ?', a) }

but that said, I've just started looking at Arel, seems pretty cool

Michael K Madison
  • 2,242
  • 3
  • 21
  • 35