I am rails noob and still trying to wrap my mind around how querying associative data works. Here is my simple schema:
create_table "microposts", :force => true do |t|
t.text "content"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "users", :force => true do |t|
t.string "name"
t.string "email"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
My Associations are as follows:
class Micropost < ActiveRecord::Base
attr_accessible :content, :user_id
belongs_to :user
accepts_nested_attributes_for :user
end
class User < ActiveRecord::Base
attr_accessible :email, :name
has_many :microposts
accepts_nested_attributes_for :microposts
end
What I am trying to do is query my microposts in such a way that they include an author attribute that corresponds to the user's name in the user table. Here is my html:
<% #microposts.each do |micropost| %>
<tr>
<td><%= micropost.content %></td>
<td><%= micropost.user_id %></td>
<td>
**<%= micropost.author %>**
</td>
<td><%= link_to 'Show', micropost %></td>
<td><%= link_to 'Edit', edit_micropost_path(micropost) %></td>
<td><%= link_to 'Destroy', micropost, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
How do get an attribute like microposts.author in one of cells above? I tried querying Microposts.users.name but it seems to return ruby object data like this:
[#<Micropost id: 1, content: "Though good for getting a general overview of Rails...", user_id: 2, created_at: "2012-09-02 01:52:47", updated_at: "2012-09-02 01:52:47">, #<Micropost id: 2, content: "This is another", user_id: 2, created_at: "2012-09-02 01:53:09", updated_at: "2012-09-02 01:53:09">, #<Micropost id: 3, content: "A close cousin of create_table is change_table,
What's more the data contains no mention of user name data. What am I doing wrong? How can I get micropost.author to work?
The belongs_to association works by storing (in your case) the user_id in the Micropost. This allows you to reference the User the Micropost belongs to like this:
micropost.user
And at this point you have access to any of the user attributes, such as name:
micropost.user.name
Edit
Two more things:
1) accepts_nested_attributes_for declarations are typically made in the parent class. They provide you with the ability to make calls like this:
# when creating, it infers the user_id attribute thanks to the nested attribute declaration
user.microposts.create(content: "blah blah")
# similarly, user_id is inferred here as well
user.microposts.where( ... )
Including the declaration in your Micropost model implies you intend to create a user (or search for one) from a micropost. Unnecessary in your use cases I think.
2) If you wanted to alias user as "author", you could replace the belongs_to call in Micropost with this:
belongs_to :author, class_name: "User", foreign_key: "user_id"
Which would allow you to query the author name as follows:
micropost.author.name
<%= micropost.user.name %>
You call user to get to the associated user, then call name to get that record's name attribute.
Related
I am building a repo that imitates Evernote, and I have established the relationship between the models and their respective columns. Among them, I rely on the column email in the model User to identify the user.
However, when I try to print <%= note.user.email %> in index.html.erb, I get an "undefined method `email' for nil:NilClass" error. I don't understand, I have established valid has_many and belongs_to, and email is also an actual column. note is derived from the entity variable #note in the controller (where other fields are valid), I don't understand which link is wrong.
This is part of the schema
create_table "users", force: :cascade do |t|
t.string "nickname"
t.string "password"
t.string "email"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
This is part of model User
class User < ApplicationRecord
validates :nickname, presence: true
validates :email, presence: true
validates :password, presence: true, confirmation: true
before_create :encrypt_password
has_many :notes
This is model Note
class Note < ApplicationRecord
validates :title, presence: true
validates :content, presence: true
default_scope { where(deleted_at: nil) }
belongs_to :user
end
This is part of NotesController
def index
#notes = Note.includes(:user).order(id: :desc)
end
this is index.html.erb
<table>
<tr>
<td>Author</td>
<td>Title</td>
<td>Actions</td>
<% #notes.each do |note| %>
<tr>
<td>
<%= note.user.email %>
</td>
<td>
<%= link_to note.title, note_path(note) %>
</td>
<td>
<%= link_to "TO EDIT", edit_note_path(note) %>
</td>
<td>
<%= link_to "TO DELETE", note_path(note), method: 'delete', data: { confirm: "確定嗎?" } %>
</td>
</tr>
<% end %>
</table>
undefined method `email' for nil:NilClass"
This error means you are looking for the method email on a nilClass object which mean your note.user is nil.
Rails can't find any user related to the note. You can first check if your note as a user.
Also you should check if you have a column user_id in your Note model it's needed for making the belongs_to relationship working. You probably did something like this in your note migration:
create_table "notes", force: :cascade do |t|
t.belongs_to :user
...
end
If you want your view to keep rendering and ignore the error if a note doesn't have any user you can do like this.
<% if note.user.present? %>
<td>
<%= note.user.email %>
</td>
<% end %>
or even using the safe navigation operator but it has its pros & cons
<td>
<%= note.user&.email %>
</td>
I have two models: games and pickems.
Here is my schema for these models:
create_table "games", force: :cascade do |t|
t.integer "week_id", limit: 4
t.integer "home_team_id", limit: 4
t.integer "away_team_id", limit: 4
t.integer "home_score", limit: 4
t.integer "away_score", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "season_id", limit: 4
end
create_table "pickems", force: :cascade do |t|
t.integer "user_id", limit: 4
t.integer "game_id", limit: 4
t.integer "winner_id", limit: 4
t.integer "score", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Game model:
class Game < ActiveRecord::Base
belongs_to :home_team, class_name: 'Team'
belongs_to :away_team, class_name: 'Team'
belongs_to :week
belongs_to :season
end
Pickem model:
class Pickem < ActiveRecord::Base
has_one :user
has_one :game
has_one :winner, class_name: 'Team'
end
In my view, I want to display all Games that do not have a Pickem associated that is referencing it. I also want to display below all the Pickems and the game attributes associated. What do I need to be calling from the controller and/or add to the model(s) to display this information?
You will need to correct the Games/Pickem association, by adding this to the Game model:
has_one :pickem
and this to the Pickem model:
belongs_to :game
This query will retrieve all of the Games that do not have a Pickem associated:
#games_without_pickems = Game.joins(:pickem).group("games.id").having("COUNT('pickems.id') = 0").order("games.id")
change table references
This will retrieve all of the Pickems and associated Games information:
#pickems = Pickem.includes(:games).all
In your view, simply loop over both #games_without_pickems and #pickems, like this:
<table>
<th>
<td>Week</td>
<td>Home Team</td>
<td>Away Team</td>
<td>Home Score</td>
<td>Away Score</td>
<td>Season</td>
</th>
<% #games_without_pickems.each do |game| %>
<tr>
<td><%= game.week.name %></td>
<td><%= game.home_team.name %></td>
<td><%= game.away_team.name %></td>
<td><%= game.home_score %></td>
<td><%= game.away_score %></td>
<td><%= game.season.name %></td>
</tr>
<% end %>
</table>
<table>
<th>
<td>User</td>
<td>Winner</td>
<td>Score</td>
<td>Week</td>
<td>Home Team</td>
<td>Away Team</td>
<td>Home Score</td>
<td>Away Score</td>
<td>Season</td>
</th>
<% #pickems.each do |pickem| %>
<tr>
<td><%= pickem.user.name %></td>
<td><%= pickem.winner.name %></td>
<td><%= pickem.score %></td>
<td><%= pickem.game.week.name %></td>
<td><%= pickem.game.home_team.name %></td>
<td><%= pickem.game.away_team.name %></td>
<td><%= pickem.game.home_score %></td>
<td><%= pickem.game.away_score %></td>
<td><%= pickem.game.season.name %></td>
</tr>
<% end %>
</table>
That should do it.
First of all, you have
# in `pickems` table
t.integer "game_id", limit: 4
and
# in Pickem Model
class Pickem < ActiveRecord::Base
...
has_one :game
...
end
I think for has_one belongs_to, you need to put foreign_key in games table rather than pickems table and it will start making sense.
i.e.
create_table "games", force: :cascade do |t|
...
t.integer "pickem_id", limit: 4
...
end
class Game < ActiveRecord::Base
...
belongs_to :pickem
...
end
Now
I want to display all Games that do not have a Pickem associated that
is referencing it
Game.where(pickem_id: nil)
One Suggestion
Since your Game model belongs to multiple models like Pickem, Team, etc. so you can use Polymorphic Association instead.
Reason: If one of your game record belongs to pickem then other fields will be empty.
I am trying to render a list of Active Records as follows:
<% #workout_sets.each do |workout_set| %>
<tr>
<td><%= workout_set.reps %></td>
<td><%= workout_set.exercise.name %></td>
<td><%= link_to 'Show', workout_set %></td>
<td><%= link_to 'Edit', edit_workout_set_path(workout_set) %></td>
<td><%= link_to 'Destroy', workout_set, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
My AR setup looks like:
class WorkoutSet < ActiveRecord::Base
belongs_to :workout
belongs_to :exercise, class_name: 'Exercise', foreign_key: 'exercises_id'
end
class Exercise < ActiveRecord::Base
end
class Workout < ActiveRecord::Base
has_many :workout_set
end
and my schema is
create_table "exercises", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "exercises", ["name"], name: "index_exercises_on_name", unique: true
create_table "workout_sets", force: :cascade do |t|
t.integer "reps", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "exercises_id"
t.integer "workouts_id"
end
add_index "workout_sets", ["exercises_id"], name: "index_workout_sets_on_exercises_id"
add_index "workout_sets", ["workouts_id"], name: "index_workout_sets_on_workouts_id"
create_table "workouts", force: :cascade do |t|
t.string "location", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
In attempting to render the page I get the following error
undefined method `name' for nil:NilClass
When I change the path in my template to <%= workout_set.exercise %> it renders each row like 444 #<Exercise:0x007fbde9dde998> Show Edit Destroy which is what I expect.
Why is the the attempted access of the name property causing this error?
One of your WorkoutSet does not have an associated Exercise. You can enforce that a WorkoutSet has an exercise Exercise in your WorkoutSet model but there are implications to that. Mainly, you could not create a WorkoutSet without first creating the Exercise. If that's what you want then add the following to the WorkoutSet model.
validates_presence_of :exercise_id
More likely though, you just want to handle the page crashing when there is no associated Exercise.
<td><%= workout_set.exercise.name unless workout_set.exercise.blank? %></td>
That will give you a blank cell but you can do something like this to have a placeholder.
<td><%= workout_set.exercise.blank? ? "No exercise for this set" : workout_set.exercise.name %></td>
You haven't set up the relationship in the Exercise model
class Exercise < ActiveRecord::Base
has_many :workout_sets
has_many :workouts, through: :workouts_sets #not needed but good to setup
end
or if you're trying to do a 1-to-1 relationship between Exercise and WorkoutSet
class Exercise < ActiveRecord::Base
has_one :workout_set
end
Also having an 's' at the end of the foreign keys in your workout_sets table (i.e. 'workouts_id') is somewhat bad form. I'm pretty sure Rails will be smart enough to make it work but if you run into more bugs I'd try changing those to 'workout_id' and 'exercise_id'.
Schema:
create_table "reports", :force => true do |t|
t.integer "user_id"
t.string "apparatus"
t.string "capt"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", :force => true do |t|
t.string "login", :limit => 40
t.string "name", :limit => 100, :default => ""
t.string "email", :limit => 100
t.string "crypted_password", :limit => 40
t.string "salt", :limit => 40
t.datetime "created_at"
t.datetime "updated_at"
t.string "remember_token", :limit => 40
t.datetime "remember_token_expires_at"
t.string "rank"
t.integer "shift"
t.integer "access"
end
user model:
Class User < ActiveRecord::Base
has_many :reports
# bunch of other stuff thats not important
end
report model:
Class Report < ActiveRecord::Base
belongs_to :user
end
views/reports/index
<% #reports.each do |report| %>
<tr>
<td><%= report.user_id %></td> # ****THIS IS THE LINE IN QUESTION****
<td><%= report.apparatus %></td>
<td><%= report.capt %></td>
<td><%= report.body %></td>
<td><%= link_to 'Show', report %></td>
<td><%= link_to 'Edit', edit_report_path(report) %></td>
<td><%= link_to 'Destroy', report, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
I would like to be able to display the name of the user that created the report. I was under the assumption that declaring the belongs_to and has_many associations would make this possible by writing report.user.name or something like that. Where have I gone wrong?
I'm 99% sure it's because one or more of your reports do not have an associated user. Try
<%= report.user.name rescue "none" %>
When there is no value in user_id field on a report then report.user will return nil. So report.user.name would be like calling nil.name, which raises an error.
UPDATE: here's a better way:
<%= report.user.try(:name) %>
You can do:
<%= report.user.name %>
But for efficiency, in your controller you can do a join to get the users name in the same query used to fetch #reports.
This query might look something like:
#reports = Report.select("reports.id as id, reports.apparatus as apparatus, reports.capt as capt, reports.body as body, users.name as user_name").joins("LEFT JOIN `users` ON `users`.`id` = `reports`.`user_id`")
Then your output would look like:
<%= report.user_name %>
I am working on a rails application where one user class named (submitters) are able to login and once they are logged in they create videos. My videos controller is here:
class VideosController < ApplicationController
def index
#videos = Video.find :all
end
def new
#submitter = current_submitter
#video = #submitter.videos.build
end
def create
#submitter = current_submitter
#video = #submitter.videos.build(params[:video])
if #video.save
#video.convert
flash[:notice] = 'Video has been uploaded'
redirect_to :action => 'index'
else
render :action => 'new'
end
end
def show
#video = Video.find(params[:id])
end
def destroy
#video = Video.find(params[:id])
#video.destroy
flash[:notice] = "Successfully deleted the video."
redirect_to root_url
end
def update_date
#video = Video.find(params[:id])
#video.update_attributes(params[:video])
flash[:notice] = "Successfully added a launch date!"
redirect_to #video
end
end
As you can probably see, I am trying to construct the controller so that when a video is created, it is created as belonging to the submitter who upload the video (via the video new view). I am using a auth system with a current_submitter method written in the application controller.
Now it lets me upload a video fine when I am logged in as a submitter. The trouble for me is working out how to display information in my view. If I want to display some columns with information about the video and then others with information about the submitter who uploaded the video, how do I go about doing that from the controller (index action), into the index view. My current view which does not work in below:
<% title "Films Submitted" %>
<table>
<tr>
<th>Title</th>
<th>Film Type</th>
<th>Premiere</th>
<th>Company</th>
<th>Name</th>
</tr>
<% for video in #videos do %>
<tr>
<td><%= link_to video.title, video %></td>
<td><%= video.film_type %></td>
<% if video.premiere == "true" %>
<td>Premiere</td>
<% else %>
<td><%= %></td>
<% end %>
<td><%= video.submitter.company %></td>
<td><%= video.submitter.name %></td>
<td><%= link_to "Delete", video, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br><br>
<%= link_to "Upload a Video", new_video_path %>
Any suggestions or tips from rails developers would be much appreciative... I am new and trying to learn.
Video Model:
class Video < ActiveRecord::Base
belongs_to :submitter
has_attachment :content_type => :video,
:storage => :file_system,
:max_size => 50.megabytes
end
Submitter Model:
class Submitter < ActiveRecord::Base
acts_as_authentic
has_many :videos
end
Schema:
create_table "videos", :force => true do |t|
t.string "title"
t.text "description"
t.string "state"
t.datetime "created_at"
t.datetime "updated_at"
t.string "content_type"
t.integer "size"
t.string "filename"
t.string "film_type"
t.boolean "premiere", :default => false
t.date "preferred_date"
t.text "reason"
t.integer "submitter_id"
t.date "actual_date"
end
create_table "submitters", :force => true do |t|
t.string "name"
t.string "company"
t.string "email"
t.string "username"
t.string "crypted_password"
t.string "password_salt"
t.string "persistence_token"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "video_id"
end
I think you've probably only setup your associations in one direction. You already have:
class Submitter < ActiveRecord::Base
has_many :videos
end
But you also need to reciprocate that relationship in the video model:
class Video < ActiveRecord::Base
belongs_to :submitter
end
This will create the submitter method in your video model, that references the submitter it belongs to.
UPDATE:
After you updated your question with the model info, I noticed one small thing: You have a video_id column on your submitters table that doesn't need to be there. Only the "belongs_to" side of the association needs a foreign key. But this isn't causing your problem.
I think you might have a video in your database without a submitter! In the index action, since you're not checking for a valid submitter before you start using it, if even a single video record is missing a submitter_id, it'll break the view.