trouble saving to intersect table following railscast - ruby-on-rails-3

I'm trying to create nested attributes as outlined in this railscast http://railscasts.com/episodes/167-more-on-virtual-attributes?view=asciicast
In my example, I am trying to associate an activity to an image, so I have a structure of activity -> activity_image -> image.
When I save my activity, rails creates the activity and the image, but I'm not getting the intersecting table of activity_image being saved. Of course, I never actually tell rails to save this, but neither does the railscast I'm following.
Is there some way this is supposed to be defined in the models? What have I got wrong?
class Activity > ActiveRecord::Base
attr_accessible :title, :description, :activity_image_url
belongs_to :user
has_many :activity_images
has_many :image_urls, :through => :activity_images
#validates_presence_of :title, :description, :url
attr_accessor :activity_image_url
after_save :assign_image
private
def assign_image
if #activity_image_url
self.activity_image_url = ImageUrl.find_or_create_by_url(#activity_image_url)
end
end
end

Related

Nested json jbuilder for photo feed having n+1 major issues

I am having performance issues on one of my api views so I ran the Bullet gem and found some major N+1 issues with the view.
The api is being consumed so the format has to remain identical.
Bullet N+1 output:
localhost:3000/api/v1/games/1/game_feed N+1 Query detected
CompletedQuest => [:comments] Add to your finder: :include =>
[:comments] N+1 Query method call stack
/app/views/api/v1/games/game_feed.json.jbuilder:3:in block in
_b3b681b668d1c2a5691a5b3f7c15bb8e' /app/views/api/v1/games/game_feed.json.jbuilder:1:in
_b3b681b668d1c2a5691a5b3f7c15bb8e'
But I don't know how to accomplish the fix. Here are the relevant parts.
View:
json.game_feed(#game_photos) do |f|
json.extract! f, :id, :user_id, :username, :image_url_original, :comments_count, :likes_count
json.comments f.comments do |comment|
json.(comment, :username, :comment)
end
json.likes f.likes do |like|
json.(like, :id, :user_id, :username)
end
end
Controller:
#game_photos = CompletedQuest.game_photos(#game.id)
Model:
def self.game_photos(game_id)
where("completed_quests.game_id = ?", game_id).order("completed_quests.id DESC").just_photos
end
scope :just_photos, -> { where.not( image_file_name: nil ) }
Model relationships:
# CompletedQuests:
belongs_to :user
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
# Comments:
belongs_to :completed_quest, counter_cache: true
belongs_to :user
So basically for each photo in the feed, it then grabs every comment & likes for ever record - obviously this is bad and I see why, but I can't figure out how to fix it with my current structure.
Any help would be appreciated - but one thing is the structure of the JSON must remain identical.
You could include the associated comments in the query as follows:
# app/models/completed_quest.rb
def self.game_photos(game_id)
includes(:comments).where("completed_quests.game_id = ?", game_id).order("completed_quests.id DESC").just_photos
end
This will include all the associated comments in the result, so when you do f.comments in your view, there won't be a database query for comments of each f instance.

Ruby on Rails Model association

I am trying to make an application using Rails 3.2.14, but I can't wrap my head around the model associations that I have built so far. I have four models which are interconnected to give desired result but so far it hasn't been working.
Job with fields: user_id, title
Jobapplication with fields: job_id, user_id, isrefused
Resume with fields: user_id, profession
I am trying to extract all the jobs which a particular user has applied for using the jobapplication model in an instance variable for the view.
All tables containing foreign keys have belong_to associations along with has_many at the other end.
So far, I have tried like this in the controller:
def applied job
#jobapplications = Jobapplication.where('user_id = ?', current_user.id)
#jobs = #jobapplications.jobs
end
The purpose is to find jobs for which the user has put in application for.
Should I redesign the models association?
The accessors can be greatly simplified if you write your model associations like this:
class User < ActiveRecord::Base
has_many :jobs # jobs posted
has_many :job_applications # job applications posted
has_many :applied_jobs, through => :job_applications, :source => :job # jobs applied for
has_one :resume
end
class Job < ActiveRecord::Base
belongs_to :user
has_many :job_applications
has_many :applicants, :through => :job_applications, :source => :user # applicants for this job
has_many :applicant_resumes, :through => :job_applications, :source => :resume
end
class JobApplication < ActiveRecord::Base
belongs_to :user
belongs_to :job
has_one :resume, :through => :user # the resume for the application
end
class Resume < ActiveRecord::Base
belongs_to :user
end
Now you can easily find the jobs a user applied for:
current_user.applied_jobs
Or all the applicants (users applying) for a specific job:
#job.applicants
And you can see a user's resume:
current_user.resume
Or an application's resume:
#job_application.resume
Or all resumes for those applying for a specific job:
#job.applicant_resumes
This looks okay:
#jobapplications = Jobapplication.where("user_id =?", current_user.id)
but not sure about this:
#jobs = #jobapplications.jobs
What's the jobs method?
try this:
#some_controller.rb
def applied_job #note the underscore!
#jobapplications = Jobapplication.where("user_id =?", current_user.id)
end
and in the view
<% #jobapplications.each do |application| %>
#list applications here
<% end %>

Added two "belongs_to" to a Comment model but unable to get one of the associations

I am currently building very simple Comment system on Rails. The primary models are User, Albumpost, and Comment. Users can post Albumposts. For each Albumpost, Users can add Comments to the Albumpost. As a result, a Comment belongs to a User and belongs to an Albumpost.
The problem I'm having is that even with the proper associations in my models (see below), I can't get
#comment.user.name
when I'm trying to render the comments in the albumpost 'show' page (/views/albumposts/show.html.erb). When I go to the page, I can't get #comment.user.name (doesn't understand the association) and get a
"undefined method `name' for nil:NilClass"
Oddly I can get
#comment.albumpost.content
I've double-checked my models and also added the proper foreign keys to the models. Am I doing something wrong in the controllers?
Here are my models:
class Comment < ActiveRecord::Base
attr_accessible :body, :albumpost_id, :user_id
belongs_to :albumpost
belongs_to :user
end
class Albumpost < ActiveRecord::Base
attr_accessible :content
belongs_to :user
has_many :comments, dependent: :destroy
end
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_many :albumposts, dependent: :destroy
has_many :comments, dependent: :destroy
end
Here are the relevant parts of my Albumpost and Comments controllers:
class AlbumpostsController < ApplicationController
def show
#albumpost = Albumpost.find(params[:id])
#comments = #albumpost.comments
#comment = Comment.new
#comment.albumpost_id = #albumpost.id
#comment.user_id = current_user.id
end
end
class CommentsController < ApplicationController
def create
albumpost_id = params[:comment].delete(:albumpost_id)
#comment = Comment.new(params[:comment])
#comment.albumpost_id = albumpost_id
#comment.user_id = current_user.id
#comment.save
redirect_to albumpost_path(#comment.albumpost)
end
end
I think you should prefer setting objects to relations instead of setting their ids. For example, you should do this:
#comment.user = current_user
instead of
#comment.user_id = current_user.id
ActiveRecord will take care of setting corresponding *_id fields. I'm not sure how it handles the reverse. (it should autoload though, if I understand correctly)

a two-dimensional array as a params value

I just finished Ruby on Rails 3 Tutorial. The final chapter is quite complex. The tutorial throughout the book basically creates a site where Users can post Microposts. Also, each User can Follow any other User and then the following User's Microposts will show up on the original User's Micropost feed.
My question has to do with why the create action inside the RelationshipsController has the params variable contain a two-dimensional array.
Here's the code.
User
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation
has_secure_password
has_many :microposts, dependent: :destroy
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship", dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
end
Micropost
class Micropost < ActiveRecord::Base
attr_accessible :content
belongs_to :user
end
Relationship
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
end
I think this is the code that creates the two-dimensional params variable (but why?)
<%= form_for(current_user.relationships.build(followed_id: #user.id), remote: true) do
|f| %>
<div><%= f.hidden_field :followed_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
RelationshipsController
class RelationshipsController < ApplicationController
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
So maybe I just answered my own question, but I've never seen a two-dimensional array for a params variable. Can someone maybe shed some light on this?
oh, maybe I should post my routes.rb file as well:
SampleApp::Application.routes.draw do
resources :users do
member do
get :following, :followers
end
end
resources :sessions, only: [:new, :create, :destroy]
resources :microposts, only: [:create, :destroy]
resources :relationships, only: [:create, :destroy]
root to: 'static_pages#home'
end
thanks,
mike
Short Answer:
This is not a 2-dimensional array, it is a nested associative array. The reason you use it is to get at the field that you actually want.
Long Aswer:
Assumption from tutorial: When a user clicks the follow button, the goal is to call current_user.follow!(other_user). I'll walk you through how the code is achieving this. All of the magic is in the Relationship Controller and the form_for function in the view.
First, you make a form for a new relationship. Because it is a nested resource, you build it through the association.
current_user.relationships.build
But a brand new Relationship object that only corresponds to one user doesn't mean much. Instead, pass in an associative array of values to initialize the object to. In this case, the other user's id. So you assign the :followed_id attribute of the Relationship object you are building to #user.id, or the user you are trying to follow.
current_user.relationships.build(followed_id: #user.id)
When you form_for on an object, you can access the attributes of the object. In this case if we look at the Relationship model, only :followed_id is accessible.
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
Finally, we need to capture the followed_id in the form submission because the goal of the form is to be able to call current_user.follow!(other_user) when the follow button is clicked. So we pass in the followed_id as a hidden field so it is accessible in params in the controller, but also the user does not see it in the view itself.
<%= f.hidden_field :followed_id %>
Finally, when the button is clicked, because the form is for a new Relationship object, the create action is called for the Relationship controller. In there, to access the relationship object corresponding to the form you do it the same way as other forms in the tutorial -
params[:relationship]
But you don't want the relationship object, you want the user object of the one to follow so you can call follow!. This is easy. Just find the user in the database from the id. How to get the followed_id? It is an attribute of the Relationship object from the form.
params[:relationship][:followed_id]
I think its worth noting that when you make a new user object, you use params[:user]. This is just an associative array and you could access fields of it if you wanted to like
params[:user][:name]
Hopefully that made sense. It is just an nested associative array Rails uses to keep track of parameters such as those from submitting a form.

Cant mass-assign protected attributes issue

I know all the security reasons behind why mass-assigning is bad, what I cant figure out is why my app is trying to do a mass assign.
I am just trying to create a new record of my Section model and I am getting the "Can't mass-assign protected attributes" error. Below are the possible involved models. Can someone please explain to me how this is a mass-assigning? I am new to rails, so I could be missing something very simple.
class Section < ActiveRecord::Base
belongs_to :project
belongs_to :type, :foreign_key => 'type_id', :class_name => 'SectionType'
attr_accessor :order
end
class SectionType < ActiveRecord::Base
attr_accessible :name, :template
end
class Project < ActiveRecord::Base
has_many :sections
attr_accessible :description, :name, :short, :status, :subtitle, :version
def to_param
return name.gsub(/\s+/, '%20')
end
end
Any help would be greatly appreciated, I am new to rails and know this is probably a simple problem, but I have been trying to find an answer and can not.
If you're attempting to create a new Section object and that's failing, that'd be because you don't have any attributes listed as accessible inside that model. You will need to do that, using a similar call to attr_accessible as the one you have in your Project model already.