Working on a Rails 3.1 project using Devise.
My User model has_many :courses and a Course belongs_to :user.
The User has a boolean attribute miles that the Course model needs to check before saving. I'm trying to use the before_save callback in the Course model like this:
# check if user preference is miles, if so convert entered value to kilometers. All
# distances are stored as kilometers and converted to miles if necessary
before_save :convert_distance
def convert_distance
if course.user.miles
course.distance = course.distance * 1.6
end
end
Obviously I can't use course.user.miles because the new course hasn't had the user_id set yet.
So how can I check the value of the user.miles attribute? Is this something I need to do in the controller?
Actually, it depends on how you're creating the Course for the User. For example, this works in a CoursesController that also knows who the user is:
course = user.courses.build(params[:course])
if course.save
# ...
end
Using #build on an association assigns the appropriate parent id, so the user will be visible from the course.
You could use a after_commit callback in this case.
Related
I have a gamification app that has four types of points, and the sum of all these kinds is the total points for a user, I want to be able to do sum and scopes on that column, so I think I should have it as a column in the DB.
scope :points_rank, -> { order(points: :desc) }
I was using a before_save for adding all four point types and storing it in points, but now I'm using a gem that does increment to these types of points, so when it updates those values, the before_save is not called, hence not updating the points value as expected.
What is the correct ActiveRecord callback to be using instead of before_save, or what else could I be doing to keep the column updated.
Try using the after_touch callback instead.
after_touch callback is triggered whenever an object is touched.
So, whenever point type changes, it should update the points.
First of all, counter_culture seems to be a way to enhance the counter_cache functionality of rails...
Used to cache the number of belonging objects on associations. For example, a comments_count column in a Post class that has many instances of Comment will cache the number of existent comments for each post.
It might not be exactly what you want, judging from your question.
Okay I get it. You're using points in your User model to create a "cached" column which can be used for wider application functionality. Okay that's cool...
--
Your setup, then, will look something like this (you were manually setting the counter_cache column, and now the gem handles it):
#app/models/user.rb
class User < ActiveRecord::Base
counter_cache :points
end
#app/models/point.rb
class Point < ActiveRecord::Base
belongs_to :user, counter_cache: true
end
The question is then that when you update the points model, you need to be able to update the "cached" column in the users model, now without any callbacks.
What is the correct ActiveRecord callback to be using instead of before_save
I'm presuming you're calling before_save on your User model (IE adding the associated data and putting the points column?
If so, you should try using a callback on the Point model, perhaps something like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :points
end
#app/models/point.rb
class Point < ActiveRecord::Base
belongs_to :user, inverse_of: :points
after_commit :update_user
private
def update_user
if user?
user.update(points: x + y + z)
end
end
end
--
Oberservers
If you have real problems, you could look at ActiveRecord observers.
Here's an answer I wrote about it: Ruby On Rails Updating Heroku Dynamic Routes
Whether this will trigger without any callbacks is another matter, but what I can say is that it will work to give you functionality you may not have had access to otherwise:
#config/application.rb (can be placed into dev or prod files if required)
config.active_record.observers = :point_observer
#app/models/point_observer.rb
class PointObserver < ActiveRecord::Observer
def before_save(point)
#logic here
end
end
A good way to test this would be to use it (you'll have to use the rails-observers gem) with different methods. IE:
#app/models/point_observer.rb
class PointObserver < ActiveRecord::Observer
def initialize(point)
#if this fires, happy days
end
end
Let's say I have two models: User and Point.
Point belongs_to the user. Point has an 'amount'. I want to define a method in the Point model that will return the user's total points. I'd like to call it in such a manner: user.points.total
I just don't know how to define the method in the Point model in such a way that I don't have to pass it the User's ID. I'd imagine there is a simple way to do this, but my Googlefu is failing me.
Assuming that User model has
has_many :points
you can do like this in User model
def total_points
points.sum(:amount)
end
Given that you want the method defined in the Point model, you'd want to use:
class Point
def total
user.points.sum(:amount)
end
end
You can then get the total points for the user associated with a given point by invoking point.total. You can't get total points through User.points.total because User is a class and doesn't refer to a particular user.
Consider using #sum, which uses SQL sum to do an aggregation.
For example,
person.points.sum('amount')
or
# inside User.rb
def total
points.sum('amount')
end
Every time I create a new company record in rails, I need to add some default (blank) contact records at that company. Front Desk, Receiving, HR, IT and so on...they won't have any data in them besides the name, just a placeholder for the user to fill in later.
So, my company model has_many contacts, and contacts belong_to company. The contact records are static and the same for every new company that gets added, but I need to pre-populate the contacts table with data, so my users don't have to.
I've read a lot about seeding the database, but I won't be able to use the terminal every time a user dynamically creates a company, and it needs to be dynamically tied to that company, the records are not agnostic. Seeding doesn't seem to be the right thing. How should this be done?
you should use a before_save filter, which checks if an attribute is empty, and otherwise set it to the default.
Using a before_save will guard against deletions later on.
But be careful only to do this for fields which will never be empty.
class Company < ActiveRecord::Base
has_many :contacts
before_save :add_defaults
def add_defaults
contacts ||= Contact.default_list # this only sets it if it's nil
# you can implement Contact#default_list as a method, or as a scope in the contacts model
end
end
What about after_create callback in Company Model?
Smth like this:
class Company < ActiveRecord::Base
has_many :contacts
after_create :add_contacts
def add_contacts
contacts.create(name: "Some name", phone: "...", ....)
end
end
Although it notionally exists for generating test data, the FactoryGirl gem is very useful for this purpose. Use it in conjunction with the after_save approach mentioned here, and you'll have a nice place to centrally define your blank records.
I have two tables:
stores
raw_stores_data
The raw_stores_data is received from a third party daily.
I'd update certain fields of the stores model if those fields have been modified for that record in raw_stores_data.
Currently I have a bunch of conditional statements that check each of those fields. Is there any better way to code this?
new_data = raw_stores_data.all.select do |item|
item.store_id.present?
end
new_data.each do |item|
if item.field1 != item.stores.field1
...
...
...
# update record with hash of fields to update created above
end
You could add an association and special mutators to the 'raw' model that know how manipulate the 'stores' object. This serves to keep the model code in the model. Thin controller, comprehensive models, etc.
class Store < ActiveRecord::Base
has_one :raw_stores_data
end
class RawStoresData < ActiveRecord::Base
belongs_to :store
def field1=(value)
store.field1 = value
store.save!
field1 = value
end
end
I'm hand waving at some of the details, and you might want to reverse the direction of the association or make it go both directions.
EDIT:
You would use this as such:
raw_data = RawStoreData.find(param[:id]) # or new or however you get this object
raw_data.field1 = param[:field1]
The act of assigning will use the 'field1=' method, and make the change to the associated store object. If you're worried about saving unnecessarily, you could conditionalize in that method to only save if the value changed.
I hope this is clearer.
I'm trying to override the way rails apply and id to an associated object, for example:
There are 2 simple models:
class Album < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :album
end
And then I want to do this:
album = Album.new :title => 'First Album'
album.photos.build
album.save #=> true
On this case I've created a plugin that overrides the id property and replaces it to a hashed string, so what I want to do is find the methods where this album_id is being replaced for my custom method instead of the int and be able to converted before it's saved.
But I want to act globally inside Rails structure because since it will be a sort of plugin I want to make this action work on dynamic models, that's why I can't create an before_save validation on the model.
I'm not sure if it's easy to understand, but I hope someone could help me on that..
Here's a screenshot of my current table so you can see what is happening:
SQLite3 DB http://cl.ly/1j3U/content
So as you can see the album_id it's being replaced for my custom ruby object when its saved...I've disabled the plugin and then it saved normally with records 11 and 12...
I want just act on a rails action and converted with my custom methods, something like
def rails_association_replaced_method(record)
#take the record associations and apply a to_i custom method before save
super(record)
end
something like this :)
Well I hope this didn't get too complicated
Cheers
It seems if I only override theActiveRecord::Base save method do the job if handled properly
define_method 'save' do
int_fields = self.class.columns.find_all { |column| column.type == :integer }
int_fields.each do |field|
if self.attributes[field.name]
self.attributes[field.name] = self.attributes[field.name].to_i
end
end
super
end
And this shall replace all the integer fields from the Current Model applying a to_i method over the result.
Rails is unfriendly to that kind of change to the defaults. What's your end goal here?