Traversing Through Multiple Associations (Rails) - ruby-on-rails

I am a little stuck on this one. I would appreciate input.
Overview
I am trying to get the output I require in this friendship model i.e. the files the current user's friends have uploaded.
Models
Like all other friendship models, User is self-referencing as :friend.
class Share < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User"
end
This is my Paperclip model:
class Upload < ActiveRecord::Base
belongs_to :user
has_attached_file :document
end
This one is generated through Devise:
class User < ActiveRecord::Base
attr_accessor :login
has_attached_file :image, :styles => { :medium => "120x120!" }
has_many :uploads
has_many :shares
has_many :friends, :through => :shares
has_many :inverse_shares, :class_name => "Share", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_shares, :source => :user
end
Attempts
The furthest I have gotten into would be the level in which I am able to output the user's friends' :username(s), where #check = current_user in the controller:
<% #check.shares.each |j| %>
<%= j.friend.username %>
<% end %>
Question
How do I output the :file_name, :file_type, and :document of each of the current user's friends? I intend to have my page look like this:
User's Friend 1 Files
File Name
File Type
Document
User's Friend 2 Files
File Name
File Type
Document
Thank you.

Per the Paperclip documentation :
Paperclip will wrap up to four attributes (all prefixed with that
attachment's name, so you can have multiple attachments per model if
you wish) and give them a friendly front end. These attributes are:
<attachment>_file_name
<attachment>_file_size
<attachment>_content_type
<attachment>_updated_at
Given that, and with your attachment name of "document" :
<% current_user.shares.each do |share|%>
<%= share.friend.username %>
<% share.friend.uploads.each do |upload| %>
<%= upload.document_file_name %>
<%= upload.document_content_type %>
<% end %>
<% end %>

Related

Is it possible to validate a grouped_collection_select in Rails?

I am working on a Rails app where users can post Quotes about different art forms (e.g. Music, Painting, Photography, etc.). Each Quote must be assigned a Medium (such as Music or Photography) and a Genre (such as Jazz or Rock and Roll for music or Landscape for photography).
I am using a grouped_collection_select for the Genre field on my Quote form, and that works great to sort the genres, but I'd like to prevent anyone from selecting a genre that doesn't belong to the medium they have selected.
I know that I can do this dynamically with javascript, as in this Railscast, but I'd like to create a validation to ensure that no bad data gets into the database.
Is there a way to validate this in my Quote model so that there's no way a Quote can be saved with a genre that does not have the correct medium? This would prevent someone from saving something that is the medium "Photography" and the genre "Jazz", for instance.
Here are my models:
class Quote < ApplicationRecord
belongs_to :medium
belongs_to :genre
end
class Medium < ApplicationRecord
has_many :quotes
has_many :genres
end
class Genre < ApplicationRecord
has_many :quotes
belongs_to :medium
end
And here are the fields on my Quote form:
<%= f.label :medium, 'Medium' %>
<%= f.collection_select :medium_id, Medium.order(:name), :id, :name, {prompt: 'Select a medium'}, {class: 'form-control'} %>
<%= f.label :genre, 'Genre' %>
<%= f.grouped_collection_select :genre_id, Medium.order(:name), :genres, :name, :id, :name, {prompt: 'Select a genre'}, {class: 'form-control'} %>
You can use rails validate method to achieve this,
validate: check_medium
def check_medium
errors.add(:base, "Your error message") if genre.try(:medium) != medium
end
My proposal here is to story only genre_id in quotes table. This belongs_to :medium is unnecessary because each genre already knows its medium. With architecture like this, you don't have to worry about medium-genre mismatch at all. Models:
class Quote
belongs_to :genre
# With delegation you still can do something like quote.medium
delegate :medium, to: :genre
end
class Medium
has_many :genres
has_many :quotes, through: :genres
end
class Genre
belongs_to :medium
has_many :quotes
end

Modelling nested models in rails

I've been grappling with a problem which is proving to be quite hard. I've got a User model, a Photo model and a comment model. Now the way my website works is that a User can have many comments on a particular photo. On the reverse side, a comment can only belong to a particular user on a particular photo.
I've read through the Active Record Associations docs and from what I've gathered is that we can't use a has_many :through associations since it appears to work for polymorphic associations between models. I'm wondering if one can use has_many :through association on one side and belongs_to association on the reverse side.
Any tips, pointers and advice? I'm just beginning in Ruby on Rails
Thanks.
Wouldn't this work?
class User
has_many :photos
has_many :comments
end
class Photo
belongs_to :user
has_many :comments
end
class Comment
belongs_to :user
belongs_to :photo
end
User has many photos and comments (the ones he uploaded/written), and each comment belongs to user (writer) and a photo which was commented on.
#app/models/user.rb
class User < ActiveRecord::Base
has_many :photos
has_many :comments, through: :photos #-> probably won't work but I'd try it first
end
#app/models/photo.rb
class Photo < ActiveRecord::Base
belongs_to :user
has_many :comments do
def current_user #-> photo.comments.current_user
where user_id: self.user_id
end
end
end
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :photo
belongs_to :user
end
--
You could access the photo's comments as follows:
<% #user.photos.each do |photo| %>
<%= photo.comments.each do |comment| %>
<%= comment %>
<% end %>
<% end %>
If you wanted to show only the user's comments, you'd be able to use the current_user ActiveRecord Association Extension:
<% #user.photos.each do |photo| %>
<%= photo.comments.current_user.each do |comment| %>
<%= comment %> #-> comments where user_id will be the same as photo's user_id
<% end %>
<% end %>
You can do it like this:
User
has_many :comments
Photo
has_many :comments
belongs_to :user
Comment
belongs_to :user
belongs_to :photo

has_many through form not registering in the join table

I have a problem with my ruby on rails 4 app with the activeadmin gem
I have generated multiple resources that works well but I have a problem with a resource that has a has_many through relationship. The new records are not registered through the join table.
Here is my model :
video.rb
class Video < ActiveRecord::Base
has_many :album_videos
has_many :albums, :through => :album_videos
end
Album.rb
class Album < ActiveRecord::Base
has_many :album_videos
has_many :videos, :through => :album_videos
end
Album_video.rb
class AlbumVideo < ActiveRecord::Base
belongs_to :video
belongs_to :album
end
And the activeadmin file for album
album.rb
ActiveAdmin.register Album do
menu :priority => 6, :label => proc{ "Album de vidéos" }
filter :album_videos
permit_params :titre, :description, videos_attributes: [:id, :titre, :_update,:_create]
form do |f|
f.inputs
f.inputs "Vidéos" do
f.input :videos, as: :check_boxes, collection: Video.all, :member_label => :titre
end
f.actions
end
end
Album's form is well displayed, I have a panel with all my registered videos.
When I create a new album, the album is registered but nothing in the album_videos table.
Anything I am missing ?
I found the solution. You juste have to add
video_ids: []
to your permit_params.
Mine looks like this now :
permit_params :titre, :description, video_ids: []

Rails: How to add a sum attribute from polymorphically associated model to retrieved records

I have the following model structure: A Carnival has many Events, which has many Competitors. A competitor has a polymorphic Participant, which is either a Student, or a Team (which has many students). In code:
class Carnival < ActiveRecord::Base
has_many :events
end
class Event < ActiveRecord::Base
belongs_to :event_type
belongs_to :division
has_and_belongs_to_many :genders
has_and_belongs_to_many :grades
has_many :competitors
has_many :students, through: :competitors, :source => :participant, :source_type => 'Student'
belongs_to :carnival
end
class Competitor < ActiveRecord::Base
belongs_to :event
belongs_to :participant, polymorphic: true
belongs_to :grade
belongs_to :house
end
class Team < ActiveRecord::Base
has_one :competitor, :as => :participant
has_one :event, :through => :competitor
has_and_belongs_to_many :students
end
class Student < ActiveRecord::Base
belongs_to :grade
belongs_to :house
belongs_to :gender
has_many :competitors, :as => :participant
has_many :events, :through => :competitors
has_many :carnivals, :through => :events
has_and_belongs_to_many :teams
end
There is a points attribute on Competitor, so that after an Event is run points are assigned to them.
What I would like is a series of methods that would give me all Students along with their total points.
Student.with_total_points_for_carnival(c)
Student.with_individual_points_for_carnival(c)
Student.with_team_points_for_carnival(c)
I got some SQL that does total points (though I'm a little uncomfortable with how/if it works properly, since I only really stumbled upon it):
SELECT
name,
SUM(points) as points
FROM
public.students_teams,
public.students,
public.teams,
public.competitors,
public.events
WHERE
students_teams.team_id = teams.id AND
students.id = students_teams.student_id AND
competitors.participant_id = teams.id AND
events.id = competitors.event_id AND
carnival_id = 1
GROUP BY
public.students.id
ORDER BY
points DESC
This gives total points. My Rails attempts give me results, but they aren't correct, and they aren't student records either:
def self.with_total_points_for(carnival)
Student.
joins(:competitors, :events, teams: { competitor: :event}).
where('events.carnival_id = ?', [carnival.id]).
group('students.id').
sum('competitors.points')
end
Any guidance as to how to get this working? So that I could go:
<% Student.with_total_points_for(#carnival).each do |s| %>
<%= s.name %>: <%= s.total_points %> <br>
<% end %>
Even better would be a function or scope like Student.with_all_points_for which would let me do:
<% Student.with_all_points_for(#carnival).each do |s| %>
<%= s.name %>: <%= s.total_points %> / <%= s.individual_points %> / <%= s.team_points %> <br>
<% end %>
but baby-steps for now...
I am not sure I caught your aim correctly but I think it might help you
Add needed fields to select
def self.with_total_points_for(carnival)
Student.
joins(:competitors, :events, teams: { competitor: :event}).
where('events.carnival_id = ?', [carnival.id]).
group('students.id').
select('students.*, SUM(competitors.points) AS total_points')
end
Some notes:
(I don't check it but I have similar issue before) if you use Postgres you shouldn't use students.* you should list all needed columns directly: select('students.id, students.name, ..., total_points')
(it can be useful) you can list in select any columns from involved tables but remember that you use group in the query
PS I am not sure that there is an easy way to get individual_points and team_points in one simple query. I think you should use subqueries for this purposes and insert them into main query. Be careful because it might be a heavy request to DB and an optimization will be required.

How to reference attributes in deep associations

Summary
Rails 3.2 with RefineryCMS 2.0. These are my pseudocode models:
Industry
name
has_many companies
has_many works through companies
Company
name
has_many works
belongs_to industry
Work
name
belongs to company
From an instance of Work, I can say work.company.name and get a the name of the associated company. I would expect it to follow that I could also say company.industry.name without a problem. However, I am getting an unhelpful error:
wrong constant name Refinery:Industries
What I would ultimately like to do is follow my associations all the way up ie work.company.industry.name, but the chain is broken between company and industry it seems.
What am I doing wrong here? Here's my code in more detail.
Code
Here are my models. Any idea what would prevent me from accessing industry attributes from an associated company given that industries have_many companies (companys lol) and companies belong_to an industry? Any help would be much appreciated.
Industry Model
module Refinery
module Industries
class Industry < Refinery::Core::BaseModel
...
attr_accessible :name, :description, :position
has_many :companys, :class_name => '::Refinery::Companys::Company', :dependent => :nullify
has_many :works, :through => :companys
end
end
end
Company Model
module Refinery
module Companys
class Company < Refinery::Core::BaseModel
...
attr_accessible :name, :position, :industry_id
has_many :works, :class_name => '::Refinery::Works::Work', :dependent => :destroy
belongs_to :industry, :class_name => '::Refinery:Industries::Industry'
end
end
end
Work Model
module Refinery
module Works
class Work < Refinery::Core::BaseModel
...
attr_accessible :name, :description, :position, :company_id
belongs_to :thumbnail, :class_name => '::Refinery::Image'
belongs_to :Company, :class_name => '::Refinery::companys::company'
end
end
end
Then in my erb file I'm doing this:
<% #works.each do |work| %>
...
<h5>
<%= work.company.name %>
</h5>
<% end %>
That one works.
This one gives me an error though:
<% #clients.each do |client| %>
<h5>
<%= client.industry.name %>
</h5>
<% end %>
That error reads:
wrong constant name Refinery:Industries
There is at least a double colon missing in your Company model:
belongs_to :industry, :class_name => '::Refinery:Industries::Industry'
should be
belongs_to :industry, :class_name => '::Refinery::Industries::Industry'
I haven't really looked at the rest of the code, but this is a first error.

Resources