Access SQL computed columns through ActiveRecord - mysql

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 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

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

Related

Copying Parent model attribute to all of its children with same attribute in mysql or Rails as single Query

I know we need to use the following sudo code in case of Rails
Parent.all.each do |parent|
parent.childrens.update_all(:price => parent.price)
end
But I have like 5 Million Parent records and I know this would take a lot of time.
Is there a easy way to do the above through Rails or MySQL the fastest way (in a single query)
Parent.includes(:childrens).find_in_batches.find_in_batches do |group|
sleep(50)
group.each { |parent| parent.childrens.update_all(price: parent.price) }
end
This is the best you can come up with rails atleast..it will avoid n+1 also, since the records are huge, find_in_batches will help you, otherwise there is a possibility that your db/dyno gets locked..
I think you can use ActiveRecord callback functionality to achieve this.
Example code would look like this:
class Parent < ActiveRecord::Base
after_update :denormalize
has_many :children
private
def denormalize
children.update_all(price: price)
end
end
This will ensure that, whenever a parent object is modified the child will also be updated.

Yii2 is there a way to specify tablename in ActiveQuery conditions (like andWhere) in a nice and short way

I make a query (with \yii\db\ActiveQuery) with joins, and some fields in "where" clause become ambigous. Is there a nice and short way to specify the name of the current model`s (ActiveRecord) table (from which one the ActiveQuery was instantiated) before the column name? So I can use this all the time in all cases and to make it short.
Don't like doing smth like this all the time (especially in places where there're no joins, but just to be able to use those methods with joins if it will be needed):
// in the ActiveQuery method initialized from the model with tableName "company"
$this->andWhere(['{{%company}}.`company_id`' => $id]);
To make the "named scopes" to work for some cases with joins..
Also, what does the [[..]] mean in this case, like:
$this->andWhere(['[[company_id]]' => $id]);
Doesn't seem to work like to solve the problem described above.
Thx in advance!
P.S. sorry, don't have enough reputation to create tag yii2-active-query
to get real table name :
Class :
ModelName::getTableSchema()->fullName
Object :
$model::getTableSchema()->fullName
Your problem is a very common one and happens most often with fields liek description, notes and the like.
Solution
Instead of
$this->andWhere(['description'=>$desc]);
you simply write
$this->andWhere(['mytable.description'=>$desc]);
Done! Simply add the table name in front of the field. Both the table name and the field name will be automatically quoted when the raw SQL is created.
Pitfall
The above example solves your problem within query classes. One I struggled over and took me quite some time to solve was a models relations! If you join in other tables during your queries (more than just one) you could also run into this problem because your relation-methods within the model are not qualified.
Example: If you have three tables: student, class, and teacher. Student and teacher probably are in relation with class and both have a FK-field class_id. Now if you go from student via class to teacher ($student->class->teacher). You also get the ambigous-error. The problem here is that you should also qualify your relation definitions within the models!
public function getTeacher()
{
return $this->hasOne(Teacher::className(), ['teacher.id' => 'class.teacher_id']);
}
Proposal
When developing your models and query-classes always fully qualify the fields. You will never ever run into this problem again...that was my experience at least! I actually created my own model-gii-template. So this gets solved automatically now ;)
Hope it helped!

ActiveRecord model column not updating (even though save! succeeds)

I've got a really, really odd problem manifesting on a big Rails e-commerce app and thought I'd see if anyone has good insight. I have an"Order" model with many associations. If I create a new instance, and then set one particular column value and "save!" the "save!" is succeeding without errors, but the change isn't actually persisted to the DB. I'll run through the scenario below:
#order = Order.create!(<some attributes>)
=> true
#order.shipping_method_id
=> 1
#order.shipping_method_id = 203
=> 203
#order.save!
=> true
#order.shipping_method_id
=> 1
To try and debug this I actually prepended a before_save filter and I can see that when this first filter is called after setting the value, it is correct ("203") BUT the very next before_save after the 6-or-so built-in "autosave_foo_bar_quux" filters (for nested associations) it is back to "1".
Oddly, if I just reload the order (#order.reload), change the column value and save! the update does succeed.
In both cases, doing #order.changed shows that ActiveModel recognizes the column value change for shipping_method_id. In the first, though, the SQL logging shows that the order row is not updated.
I feel like I'm going insane. Any ideas? Also, let me know if there's anything else I can post here for more context.

Rails MySQL auto generated column

I am working on RoR 3.x with MySQL as backend.
Is there any way to modify the existing id (autogenerated with migration) in a way that can generate particular user defined pattern.
For e.g : "Products Table" should have values in "id" field like "P01", "P02" and so on, where P can be specified by the user, and 01,02 are autogenerated.
Thanks in advance!
The 'regular' IDs (1, 2, 3, ..., n) in this case aren't generated by rails but by MySQL (using AUTO_INCREMENT). So, if you want to go with auto-generated, auto-incrementing IDs, I would suggest not messing with this. What you could do, and what I would suggest, is creating an additional column and then populating that using a callback on your model.
Example:
class Product < ActiveRecord::Base
attr_accessor :user_supplied_prefix
after_create :generate_user_supplied_id
private
def generate_user_supplied_id
update_attribute(:user_supplied_id, "#{self.user_supplied_prefix}#{self.id}")
end
end
The downside of this approach is that Product.find(user_supplied_id) won't work. Fortunately, Product.find_by_user_supplied_id(user_supplied_id) will.

Indexing calculated field for search conditions in Thinking Sphinx

I have a products model set up that I am trying to search with Thinking Sphinx. The model has an attribute called status which can be Active, Not active or Active during specified dates.
I would like to be able to restrict my search results to products that are active. I.e. has status of active or has status of active during dates and the current time is between those dates.
I'm a beginner to Rails so I'm looking for suggestions as to how I could implement this. I thought about putting a boolean method in my model that calculates this logic, but I don't think that this is indexable by Sphinx.
I am using MySQL as the database server.
Does anyone have any bright ideas?
You're right, ruby methods on your model are not accesible to sphinx. However you can re-create the method as a sphinx attribute. These can easily be made using SQL fragments like so:
class Person < ActiveRecord::base
...
define_index do
has "status = 'active' and now() > start_date and now() < end_date", :as => :active, :type => :boolean
end
...
end
Using a string to specify a field or attribute like this is the same as specifying a custom column when building an SQL query.
try the #indexes method (haven't tried myself, just noticed in googling around)
http://www.slideshare.net/sazwqa/using-thinking-sphinx-with-rails-presentation
slide 11
http://rdoc.info/rdoc/freelancing-god/thinking-sphinx/blob/04320b610b3a665ca1885cc2e6f29354d029e49a/ThinkingSphinx/Index/Builder.html#indexes-instance_method
Also:
http://www.mail-archive.com/rubyonrails-deployment#googlegroups.com/msg02046.html