I have something like this:
class Post < ActiveRecord::Base
has_many :tag_members
has_many :tags, through: :tag_member
end
class Tag < ActiveRecord::Base
has_many :tag_members
has_many :posts, through: :tag_member
end
class TagMember < ActiveRecord::Base
belongs_to :tag
belongs_to :image
end
I want to track the edits on the post object. The easiest way to do this appears to be something like this:
class Post < ActiveRecord::Base
before_update :save_edits
def save_edits
# Assuming save_edit takes in a hash and persists it somehow
save_edit(self.changes)
end
end
However, from testing I've done, adding a new Tag to the has_many association on a Post does not run the before_update callback, and does not store anything in the hash returned by .changes.
What is the best way to track these types of edit as well? Should I simply overload the .tags= method to do my own storage, or is there a better way?
You could do something like this:
class TagMember < ActiveRecord::Base
after_save { |t| t.post.save }
# ^^^^
belongs_to :tag
belongs_to :image
belongs_to :post
# ^^^^
end
Related
I have the following models set up:
class Location < ActiveRecord::Base
has_many :location_parking_locations
has_many :parking_locations, through: :location_parking_locations
end
class LocationParkingLocation < ActiveRecord::Base
belongs_to :location
belongs_to :parking_location
end
class ParkingLocation < ActiveRecord::Base
has_many :location_parking_locations
has_many :locations, through: :location_parking_locations
end
The LocationParkingLocation has an integer field called upvotes. I would like to create a 'by_votes' scope that I can add to a query to order the results by this upvotes field. Where and how do I define this scope, so that I can call it like this:
location.parking_locations.by_votes
I can't define it like this, because then it's not a valid method on parking_locations:
class LocationParkingLocation < ActiveRecord::Base
belongs_to :location
belongs_to :parking_location
scope :by_votes, -> { order("upvotes DESC") }
end
Should it be defined in the 'ParkingLocation' class? If so, how do I tell it that I want to order by a field on the location_parking_locations table?
I think you might be able to use merge here.
You can leave your scope in the LocationParkingLocation class, and the result would look like:
location.parking_locations.merge(LocationParkingLocation.by_votes)
I just read a little about it in this blog post.
We've got 2 models & a join model:
#app/models/message.rb
Class Message < ActiveRecord::Base
has_many :image_messages
has_many :images, through: :image_messages
end
#app/models/image.rb
Class Image < ActiveRecord::Base
has_many :image_messages
has_many :messages, through: :image_messages
end
#app/models/image_message.rb
Class ImageMessage < ActiveRecord::Base
belongs_to :image
belongs_to :message
end
Extra Attributes
We're looking to extract the extra attributes from the join model (ImageMessage) and have them accessible in the Message model:
#message.image_messages.first.caption # -> what happens now
#message.images.first.caption #-> we want
We've already achieved this using the select method when declaring the association:
#app/models/message.rb
has_many :images, -> { select("#{Image.table_name}.*", "#{ImageMessage.table_name}.caption AS caption") }, class_name: 'Image', through: :image_messages, dependent: :destroy
Delegate
We've just found the delegate method, which does exactly what this needs. However, it only seems to work for has_one and belongs_to associations
We just got this working with a single association, but it seems it does not work for collections (just takes you to a public method)
Question
Do you know any way we could return the .caption attribute from the ImageMessage join model through the Image model?
We have this currently:
#app/models/image.rb
Class Message < ActiveRecord::Base
has_many :image_messages
has_many :messages, through: :image_messages
delegate :caption, to: :image_messages, allow_nil: true
end
#app/models/image_message.rb
Class ImageMessage < ActiveRecord::Base
belongs_to :image
belongs_to :message
def self.caption # -> only works with class method
#what do we put here?
end
end
Update
Thanks to Billy Chan (for the instance method idea), we have got it working very tentatively:
#app/models/image.rb
Class Image < ActiveRecord::Base
#Caption
def caption
self.image_messages.to_a
end
end
#app/views/messages/show.html.erb
<%= #message.images.each_with_index do |i, index| %>
<%= i.caption[index][:caption] %> #-> works, but super sketchy
<% end %>
Any way to refactor, specifically to get it so that each time .caption is called, it returns the image_message.caption value for that particular record?
delegate is just a shorthand as equivalent instance method. It's not a solution for all, and there are even some debate that it's not so explicit.
You can use an instance method when simple delegate can't fit.
I reviewed and found any association is unnecessary is this case. The ImageMessage's class method caption is more like a constant, you can refer it directly.
def image_message_caption
ImageMessage.caption
end
I need to pass updated paramaters to back to a parent model when saving a series of its children.
For example if a save a bunch of employees to each task through a project, I need to let the project know the title of some of its tasks have changed, then I need to collect all the titles that changed and process them in the ProjectObserver. Is this possible?
I realize there might not be a way to make this work the way I'm trying. If not I'm happy to hear suggestions about how I might be able to get around this.
Here is what I have tried without any success:
class Employee < ActiveRecord::Base
has_many :employee_tasks
has_many :tasks, :through => :employee_tasks
accepts_nested_attributes_For :employee_tasks
accepts_nested_attributes_For :tasks
end
class Project < ActiveRecord::Base
attr_accessor :changed_employees
has_many :tasks
end
class Task < ActiveRecord::Base
has_many :employee_tasks
has_many :employees, :through => :employee_tasks
belongs_to :project
accepts_nested_attributes_For :employee_tasks
end
class EmployeeTask < ActiveRecord::Base
#this is what I want to accomplish
before_save do
if self.employee_id_changed
self.task.project.changed_employees ||= []
self.task.project.changed_employees << self.employee_id_changed
end
end
belongs_to :task
belongs_to :employee
end
class ProjectObserver < ActiveRecord::Observer
observe :project
def after_save(project)
puts project.changed_employees
# should print out the changed attributes loaded from EmployeeTask
#send a single email with all the updated titles (not one email for each change)
end
end
Sounds like you need to use the after_save method described here http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
I have three models which look something like this:
Class User < ActiveRecord::Base
has_many :comments
end
Class Comment < ActiveRecord::Base
belongs_to :user
has_many :votes
end
Class Vote < ActiveRecord::Base
belongs_to :comment
end
Now I want to get all the votes associated with a user's comments like so:
#user.comments.votes
But this throws the error:
undefined method `votes' for #<ActiveRecord::Relation:0x3f6f8a0>
This seems like it should work, but I suspect ActiveRecord is coughing on the deeper has_many relationship. I've hacked together an SQL query that gets the desired results, but I suspect there's a cleaner way using purely ActiveRecord. Any tips?
You should use a has_many :through association
In your case it would be
Class User < ActiveRecord::Base
has_many :comments
has_many :votes, :through => :comments
end
Class Comment < ActiveRecord::Base
belongs_to :user
has_many :votes
end
Class Vote < ActiveRecord::Base
belongs_to :comment
end
And then simply get the votes with
#user.votes
Try this:
Vote.joins(comment: :user).where(users: {id: #user.id})
This seems to be a fairly common problem over here, yet there is no definitive solution. To restate once again, say I have a model:
def Model < ActiveRecord::Base
has_many :somethings, ...
has_many :otherthings, ...
end
The question is then how to add a third association :combined that combines the two? I know this can be done with :finder_sql and similar result can be achieved with a scope, but neither of these gives me an actual association. The whole point of this is to be able to use it for another association with :through and things like Model.first.combined.some_scope.count
EDIT: the relevant portions of the actual code
class Donation < ActiveRecord::Base
# either Project or Nonprofit
belongs_to :donatable, :polymorphic => true
belongs_to :account
end
class Project < ActiveRecord::Base
belongs_to :nonprofit
end
class Nonprofit < ActiveRecord::Base
has_many :projects
# donations can be either direct or through a project
# the next two associations work fine on their own
# has_many :donations, :as => :donatable, :through => :projects
# has_many :donations, :as => :donatable
has_many :donations, .... # how do I get both here,
has_many :supporters, :through => :donations # for this to work?
end
Thanks.
If Something and Otherthing are sufficiently similar, use STI:
def Model < ActiveRecord::Base
has_many :somethings
has_many :otherthings
has_many :genericthings
end
def Genericthing < Activerecord::Base
# put a string column named "type" in the table
belongs_to :model
end
def Something < Genericthing
end
def Otherthing < Genericthing
end