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
Related
I recently updated my knowledgebase app to have a pretty standard has_many through association. Previously articles belonged to a category and a category had many articles. Now it is situated as the following:
class Article < ApplicationRecord
has_many :categorizations
has_many :categories, :through => :categorizations
class Category < ApplicationRecord
has_many :categorizations
has_many :articles, :through => :categorizations, dependent: :destroy
class Categorization < ApplicationRecord
belongs_to :article
belongs_to :category
One of the categories I have is the "Internal" category. What I am trying to accomplish is a helper method that I can us to perform actions if a particular article has the Internal category set on it. Something like the following:
if #article.internal?
do stuff
end
I am thinking it need to be in the articles_helper.rb file.
module ArticlesHelper
def internal?
figure out if #article has category "internal"
end
end
I have the following but am thinking I am on the wrong track and could use some help:
def internal?
#internal_cat = []
#article.categories.each do |n|
if (n.name == "Internal")
#internal_cat = n.name
end
end
end
Any help would be greatly appreciated!
This is the wrong use case for a helper method. Helper methods are used to simplify and DRY your views, and occasionally controllers. For example the form helpers make it easier to create forms and bind models to inputs.
Helpers are modules that are mixed into the view context.
What you want here is just a plain old method on your model as it can act on self:
class Article < ApplicationRecord
# ...
def has_category?(name)
categories.exists?(name: name)
end
def internal?
has_category?("Internal")
end
end
Later you can refactor this code into a module if needed but this is not the same thing as a helper. Rather its parallel inheritance through the mixin pattern.
module Categorized
extend ActiveSupport::Concern
included do
has_many :categorizations, as: :resource
has_many :categories, through: :categorizations
end
def has_category?(name)
categories.exists?(name: name)
end
end
class Article < ApplicationRecord
include Categorized
end
class Video < ApplicationRecord
include Categorized
end
You also want to set the dependent: :destroy option on the categorizations join table. The way you have set it up destroying an article will destroy the normalized row in categories!
class Article < ApplicationRecord
has_many :categorizations, dependent: :destroy
has_many :categories, through: :categorizations
class Category < ApplicationRecord
has_many :categorizations, dependent: :destroy
has_many :articles, through: :categorizations
I have a Region > Store > Device hierarchy, where each of these things can have a config_set.
class Region < ActiveRecord::Base
has_many :stores, dependent: :destroy
has_one :config_set, as: :configurable
end
class Store < ActiveRecord::Base
has_many :devices
has_one :config_set, as: :configurable
belongs_to :region
end
class Device < ActiveRecord::Base
has_one :config_set, as: :configurable
belongs_to :store
end
class ConfigSet < ActiveRecord::Base
belongs_to :configurable, polymorphic: true
end
From the config_set show page, I want to generate a link to whatever configurable that config_set belongs to. link_to is using polymorphic_url internally, which wants either a #device, a [#store, #device], or a [#region, #store, #device]. Is there a simple way to deal with this?
Found out a not very nice solution abusing the fact that to polymorphic_url #device and [nil, #device] are equivalent, combined with ruby's try :
<%= link_to 'Back', [#config_set.configurable.try(:store).try(:region), #config_set.configurable.try(:store) || #config_set.configurable.try(:region), #config_set.configurable] %>
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
There has to be a better way to do this. My Favorite model belongs to User while Applicant belongs to both Gig and User. I am trying to efficiently determine whether a user has applied for Gig that was favorited (<% if #application.present? %>).
I tried chaining the collection by using something like #favorites.each.gig to no avail. While the below index action for Favorites seems to work, it's really verbose and inefficient. What is a more succinct way of doing this?
def index
#favorites = Favorite.where(:candidate_id => current_candidate)
#applications = Applicant.where(:candidate_id => current_candidate)
#favorites.each do |favorite|
#applications.each do |application|
if favorite.gig.id == application.id
#application = application
end
end
end
end
class User
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Favorite < ActiveRecord::Base
belongs_to :candidate
belongs_to :gig
end
class Applicant < ActiveRecord::Base
belongs_to :gig
belongs_to :candidate
end
class Candidate < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Gig < ActiveRecord::Base
belongs_to :employer
has_many :applicants
has_many :favorites
has_many :users, :through => :applicants
end
For lack of a better answer, here's my idea:
--
User
Your user model should be structured as such (I just highlighted foreign keys, which I imagine you'd have anyway):
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants, foreign_key: "candidate_id"
has_many :favorites, foreign_key: "candidate_id"
end
This means you'll be able to call:
current_candidate.favorites
current_candidate.applicants
This will remove the need for your #applications and #favorites queries
--
Favorite
You basically want to return a boolean of whether applicant is part of the favorite model or not. In essence, for each favorite the candidate has made, you'll be able to check if it's got an application
I would do this by setting an instance method on your favorites method using an ActiveRecord Association Extension, like so:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :favorites do
def applied?
self.applicant.exists? proxy_association.owner.gig.id
end
end
end
This will allow you to call:
<%= for favorite in current_candidate.favorites do %>
<%= if favorite.applied? %>
<% end %>
This is untested & highly speculative. I hope it gives you some ideas, though!
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