I'm trying to extend the Rails Tutorial Sample App to include replies.
I created a Recipient model that contains a user_id to designate the person to whom the reply is addressed and a micropost_id.
I added the following to my User model.
class User < ActiveRecord::Base
...
has_many :replies, foreign_key: "user_id", class_name: "Recipient", dependent: :destroy
has_many :received_replies, through: :replies, source: :micropost
...
def feed
Micropost.from_followed_by_and_replying_to(self)
end
...
end
And this to my Micropost model:
class Micropost < ActiveRecord::Base
belongs_to :user
...
has_many :recipients, dependent: :destroy
has_many :replied_users, through: :recipients, :source => "user"
...
def self.from_followed_by_and_replying_to(user)
followed_ids = "SELECT followed_id FROM relationships
WHERE followed_id = :user_id"
replier_ids = "SELECT micropost_id FROM recipients
WHERE user_id = :user_id"
where("user_id in (#{followed_ids})
OR id in (#{replier_ids}) OR user_id = :user_id",
user_id: user.id)
end
...
end
The StaticPages#home action loads the feed:
class StaticPagesController < ApplicationController
def home
if signed_in?
#micropost = current_user.microposts.build
#feed_items = current_user.feed.paginate(page: params[:page])
end
end
...
end
Then when signed in and visiting the home page, I get NoMethodError in StaticPages#Home for the shared feed_item partial (app/views/shared/_feed_item.html.erb) at this line:
<%= link_to gravatar_for(feed_item.user), feed_item.user %>
It's undefined method 'email' for nil:NilClass (presumably from user.email which the gravatar_for helper method uses.
When I call Micropost.from_followed_by_and_replying_to([some user]) in the rails console, it has no trouble returning both the microposts from followed users as well as replies, so I don't think my db querying is incorrect here. Any help is appreciated, I'm really stumped.
edit: (removed some HTML from these)
app/views/static_pages/home.html.erb:
<% if signed_in? %>
...
<%= render 'shared/user_info' %>
<%= render 'shared/stats' %>
<%= render 'shared/micropost_form' %>
<%= render 'shared/feed' %>
...
<% else %>
...
<% end %>
app/views/shared/_feed.html.erb:
<% if #feed_items.any? %>
<%= render partial: 'shared/feed_item', collection: #feed_items %>
<%= will_paginate #feed_items %>
<% end %>
app/views/shared/_feed_items.html.erb:
<li id="<%= feed_item.id %>">
<%= link_to gravatar_for(feed_item.user), feed_item.user %>
<%= link_to feed_item.user.name, feed_item.user %>
...
</li>
If gravatar_for calls email on the user you pass to it, then the error message is telling you that feed_item.user is nil.
Try putting <% raise feed_item.user %> the line before the link_to, and see if it is indeed nil. Also, a stack trace of the error is one of the most useful things you can put in a SO question.
Related
Within my Ruby on Rails application I am trying to implement a relationship between Group and Contact, whereby one group can contain many contacts and one contact can be part of many groups. I am using a model called Contactgroup to deal with this relationship, and so the tables are:
Group (id, name)
Contact (id, firstname, surname)
Contactgroup (group_id, contact_id)
With example data being:
Groups:
ID Name
1 Singers
2 Drummers
Contacts:
ID Firstname Surname
1 Freddy Mercury
2 Roger Taylor
3 Kurt Cobain
4 Dave Grohl
Contact Groups:
Group_ID Contact_ID
1 1
1 3
1 4
2 2
2 4
What I am trying to do is get it so that when a user creates a group, they can select the contacts that they want to add to that group. This means that there is the group form, whereby the user types the group name, and on this form I want to display checkboxes for each of the user's contacts so that the user can select the contacts they want to add to the group, and when they click submit the new group will be saved in the Group table and the new contact group records will be saved in the Contactgroup table.
This is the app/views/groups/_form.html.erb code:
<%= form_for #group do |f| %>
<% if #group.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#group.errors.count, "error") %> prohibited this group from being saved:
</h2>
<ul>
<% #group.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<h2>Add members:</h2>
<%= form_for([#group, #group.contactgroups.build]) do |f| %>
<p>
<%= f.collection_check_boxes(:contact_id, #contacts, :id, :firstname) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
On here you can see the code I am trying to use to do this:
<h2>Add members:</h2>
<%= form_for([#group, #group.contactgroups.build]) do |f| %>
<p>
<%= f.collection_check_boxes(:contact_id, #contacts, :id, :firstname) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
I have got this from rails guides (http://guides.rubyonrails.org/getting_started.html) but I get the error undefined methodcontactgroups' for #` and don't think this will give me what I want.
My routes file is:
Rails.application.routes.draw do
get 'sessions/new'
get 'sessions/create'
get 'sessions/destroy'
resources :users
get 'welcome/index'
root 'welcome#index'
resources :contacts
resources :groups do
resources :contactgroups
end
resources :contactgroups
get 'sessions/new'
get 'sessions/create'
get 'sessions/destroy'
controller :sessions do
get 'login' => :new
post 'login' => :create
get 'logout' => :destroy
end
end
My groups_controller:
class GroupsController < ApplicationController
def index
#groups = Group.where(user_id: session[:user_id])
end
def show
#group = Group.find(params[:id])
#members = Contactgroup.where(group_id: #group.id)
end
def new
#group = Group.new
#contacts = Contact.where(user_id: session[:user_id])
end
def edit
#group = Group.find(params[:id])
end
def create
#group = Group.new(group_params)
#group.user_id = session[:user_id]
if #group.save
redirect_to #group
else
render 'new'
end
end
def update
#group = Group.find(params[:id])
if #group.update(group_params)
redirect_to #group
else
render 'edit'
end
end
def destroy
#group = Group.find(params[:id])
#group.destroy
redirect_to groups_path
end
private
def group_params
params.require(:group).permit(:name, :user_id)
end
end
And contactgroups_controller:
class ContactgroupsController < ApplicationController
def destroy
#contactgroup = Contactgroup.find(params[:id])
#contactgroup.destroy
redirect_to(:back)
end
end
My models are as follows:
Contact.rb:
class Contact < ActiveRecord::Base
end
Group.rb:
class Group < ActiveRecord::Base
end
Contactgroup.rb:
class Contactgroup < ActiveRecord::Base
belongs_to :contact
belongs_to :group
end
There must be a simple solution to solve this as I assume it is commonly done on other systems, but I am not sure how to do this.
Can someone please help.
You cannot use form inside form. The correct way to use collection_check_boxes is following.
Replace
<%= form_for([#group, #group.contactgroups.build]) do |f| %>
<p>
<%= f.collection_check_boxes(:contact_id, #contacts, :id, :firstname) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
With just
<p>
<%= f.collection_check_boxes(:contact_ids, #contacts, :id, :firstname) %>
</p>
This was much simpler than initially thought/suggested.
What I needed to do was change the models to:
Contactgroup
belongs_to :contact
belongs_to :group
Contact
has_many :contactgroups
has_many :groups, through: :contactgroups, :dependent => :destroy
Group
has_many :contactgroups
has_many :contacts, through: :contactgroups, :dependent => :destroy
In the groups_controller I needed to change the new method and params to:
def new
#group = Group.new
#group.contactgroups.build
end
private
def group_params
params.require(:group).permit(:name, :user_id, { contact_ids: [] })
end
And then add the following line of code into app/views/groups/_form.html.erb:
<%= f.collection_check_boxes :contact_ids, Contact.where(user_id: session[:user_id]), :id, :firstname ,{ prompt: "firstname" } %>
This provides me with a checkbox for each contact, and allows contactgroup records to be created from the group form.
Ok so the issue is very simple. You are calling #group.contactgroups but you haven't actually set up that association on the group model yet. only have associations set up from the contactgroup side. So you can do contactgroup.group but not group.contactgroups
Your best bet is to actually model this as habtm - as I mentioned earlier. This is how you'd do that:
Contact.rb:
class Contact < ActiveRecord::Base
has_and_belongs_to_many :groups
end
Group.rb:
class Group < ActiveRecord::Base
has_and_belongs_to_many :contacts
end
Note: you still have the concept of the contact-group for HABTM but using Rails standard naming it would be in your database as the contacts_groups table. Then you could build your forms that way.
With a quick google, here's a S/O question on using checkboxes with HABTM (haven't vetted it for usefulness to your situation): Rails 4 - checkboxes for has_and_belongs_to_many association
Using HABTM is Rails standard practice for lots of very good reasons. It really does actually fit your situation (honest!) and it does not actually break the requirement you have of wanting to see it in the SQL (seriously!).
Give it a try first :)
I can tell you how to break Rails conventions... but it's generally well-understood that you shouldn't break conventions until you know what the conventions are there for.
I don't know my heading title good or bed because I'm new in ruby on rails, I'm troubling one conditional issue like below example.
I have three table like user, post & saved_post
user table
user_id | user_name |
---------------------
1 | ABC |
---------------------
2 | efg |
post table
post_id | title |
--------------------
1 | XYZ |
--------------------
2 | xyz |
saved_post table
id | user_id | post_id |
-----------------------------
1 | 1 | 2 |
View
<% #post.each do |p| %>
<%= p.post_title %>
<%= form_for :create, url: home_path(#save), action: :create, method: :post do |f| %>
<%= f.hidden_field :post_id, :value => p.post_id %>
<button class="btn btn-default btn-save" type="submit">Save</button>
<% end %>
<% end %>
If user_id 1 save post_id 2 in the saved_post table then show this post
saved only for user_id 1, otherwise show save.
How can I reach this solution?
You can try has_many :through Association which Active Record have.
Refer this link.
class Users < ActiveRecord::Base
has_many :posts
has_many :saved_posts, through: :posts
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :saved_post
end
class SavedPost < ActiveRecord::Base
has_many :posts
has_many :users, through: :posts
end
More details regarding this you will find in above link.
Rails is all about convention over configuration. Meaning if you write your Rails applications following Rails conventions, you'll have huge benefit from it.
Your tables should be named in plural form. Tables and Posts. What I understood from your comments is that user has many posts and posts can belong to many users. This is typical has_and_belongs_to_many association.
If you rename your saved_post table as posts_users (both table names in alphabetical order) Rails will know how to deal with these tables automatically.
Database should look like this:
I made a sample application with your two user and methods to add posts to either of those users.
# routes.rb
root 'posts#index'
resources :posts
# posts_controller.rb
class PostsController < ApplicationController
before_action :set_user
def index
#posts = #current_user.posts
end
def new
#post = #current_user.posts.build(title: 'totally new title')
end
def edit
#post = Post.find(params[:id])
end
def create
#post = Post.new(post_params)
#post.save
redirect_to posts_path
end
def update
#post = Post.find(params[:id])
if #post.update(post_params)
redirect_to posts_path
else
render 'edit'
end
end
# --------------------------------------------------------------------------
private
# --------------------------------------------------------------------------
# Sets the user based on the params value
def set_user
if params[:user_id]
#current_user = User.find(params[:user_id])
else
# If not found, sets the first from the Users table
#current_user = User.first
end
end
def post_params
params.require(:post).permit(:title, {user_ids: []})
end
end
index
# index.html.erb
<h3>Users</h3>
<% User.all.each do |user| %>
<%= link_to user.username, posts_path(user_id: user.id) %> <br />
<% end %>
<h3>Posts for the user: <%= #current_user.username %></h3>
<p>
<% #posts.each do |post| %>
<%= link_to post.title, edit_post_path(post) %> <br />
<% end %>
</p>
<p>
<%= link_to 'Create new post', new_post_path %>
</p>
In edit you can choose which users are attached to this post:
# edit.html.erb
<%= render :partial => "post_form", locals: {button_name: 'update'} %>
# _post_form.html.erb
<h3>Post <%= #post.title %> for the user <%= #current_user.username %></h3>
<%= form_for #post do |f| %>
<%= f.text_field :title %> <br />
<p>
<label>
<%= f.collection_check_boxes(:user_ids, User.all, :id, :username) %>
<label><br />
</p>
<p>
<%= f.submit button_name, class: "btn btn-default btn-save" %>
</p>
<% end %>
In post.rb and user.rb files you need to specify association between these two classes. And when you have posts_users table named correctly, Rails finds it automatically.
# post.rb
class Post < ActiveRecord::Base
has_and_belongs_to_many :users
end
# user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :posts
end
And if you create a new title, if will use the same form:
# new.html.erb
<%= render :partial => "post_form", locals: {button_name: 'create'} %>
I have been trying to fix an error associated with using the Ancestry gem for comments on my app for Rails 4. I used railscast episode 262 as a guide. However, unlike the episode, my comments model is a nested resource inside another model.Before I go further, I will supply the necessary code for reference. If you like to read the error right away, it is mentioned right after all the code snippets.
The Relevant Models:
class Comment < ActiveRecord::Base
has_ancestry
belongs_to :user
belongs_to :scoreboard
end
class Scoreboard < ActiveRecord::Base
#scoreboard model is like an article page on which users can post comments
belongs_to :user
has_many :teams, dependent: :destroy
has_many :comments, dependent: :destroy
end
Relevant code in the route file:
resources :scoreboards do
resources :comments
resources :teams, only: [:edit, :create, :destroy, :update]
end
The Scoreboards Controller Method for the page on which one can post comments:
def show
#scoreboard = Scoreboard.find_by_id(params[:id])
#team = #scoreboard.teams.build
#comment = #scoreboard.comments.new
end
The Comments Controller:
class CommentsController < ApplicationController
def new
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment = #scoreboard.comments.new(:parent_id => params[:parent_id])
end
def create
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment = #scoreboard.comments.new comment_params
if #comment.save
redirect_to scoreboard_url(#comment.scoreboard_id)
else
render 'new'
end
end
private
def comment_params
params.require(:comment).permit(:body, :parent_id).merge(user_id: current_user.id)
end
end
I will include the migration for the ancestry gem if any mistakes were made on that :
class AddAncestryToComments < ActiveRecord::Migration
def change
add_column :comments, :ancestry, :string
add_index :comments, :ancestry
end
end
The following code shows the view code:
Scoreboard#show View which is giving me the error in the last line:
<div class= "comment-section">
<%= form_for [#scoreboard, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :body, class: "comment-field" %>
<%= f.hidden_field :parent_id %> #is it needed to include this here? because this form is for new comments not replies
<%= f.submit "Join the discussion...", class: " comment-button btn btn-primary" %>
<% end %>
<%= nested_comments #scoreboard.comments.reject(&:new_record?).arrange(:order => :created_at) %>
</div>
The (comments partial)_comment.html.erb View:
<div class=" comment-div">
<p> Posted by <%= link_to "#{comment.user.name}", comment.user %>
<%= time_ago_in_words(comment.created_at) %> ago
</p>
<div class="comment-body">
<%= comment.body %>
<%= link_to "Reply", new_scoreboard_comment_path(#scoreboard, comment, :parent_id => comment) %>
</div>
</div>
The helper method to render comments:
def nested_comments(comments)
comments.map do |comment, sub_comment| #the comments.map also gives me an error if I choose to render the comments without the .arrange ancestry method
render(comment) + content_tag(:div, nested_comments(sub_comment), class: "nested_messages")
end.join.html_safe
end
The new.html.erb for Comments which one is redirected to for the replies form submission:
<%= form_for [#scoreboard, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :body, class: "comment-field" %>
<%= f.hidden_field :parent_id %>
<%= f.submit "Join the discussion...", class: " comment-button btn btn-primary" %>
<% end %>
Upon creating a scoreboard, I am redirected to the show page, where i get the following error:
undefined method `arrange' for []:Array
Even though the array of comments is empty, I get the same error if it wasnt. I have tried .subtree.arrange but that gives me the same error. Also, the ancestry documentation said that .arrange works on scoped classes only. I don't know what that means. I would appreciate some help on making the page work so the comments show properly ordered with the replies after their parent comments. If this is the wrong approach for threaded comments(replies and all), I would appreciate some guidance on what to research next.
.reject(&:new_record?) this will return an array. The error sounds like arrange is a scope on ActiveRecord. So move the reject to the end and it should work.
#scoreboard.comments.arrange(:order => :created_at).reject(&:new_record?)
In regards your comment nesting, I have implemented this before, and found the Railscasts recommendation of a helper to be extremely weak.
Passing parent_id to a comment
Instead, you're better using a partial which becomes recursive depending on the number of children each comment has:
#app/views/scoreboards/show.html.erb
<%= render #comments %>
#app/views/scoreboards/_comment.html.erb
<%= link_to comment.title, comment_path(comment) %>
<div class="nested">
<%= render comment.children if comment.has_children? %>
</div>
I'm trying to add a simple Star Rating system for my app having taken this tutorial for an example. I have User, Hotel and Rating models. Dependencies are:
(rating.rb)
belongs_to :user
belongs_to :hotel
(hotel.rb) & (user.rb)
has_many :ratings
And with the following code in hotel view I get this error:
NameError in Hotels#show
undefined local variable or method `user' for Class...
(in the line with <%= form_for...)
Hotel view (show.html.erb):
<% form_id = "hotel_#{#hotel.id}_rating" %>
<% if signed_in? %> <!-- To avoid throwing an exception if no user is signed in -->
<% user_id = current_user.id %>
<% else %>
<% user_id = -1 %>
<% end %>
<%= form_for #hotel.ratings.find_or_create_by_user_id user.id,
:html => {:id => form_id,
:class => "star_rating_form"} do |f| %>
<%= f.hidden_field :hotel_id, :value => #hotel.id %>
<% if signed_in? %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<% end %>
<%= f.hidden_field :stars, :id => form_id + "_stars" %>
<% end %>
<% (1..5).each do |i| %>
<li class="rating_star" id="<%= form_id %>_<%= i %>" data-stars="<%= i %>" data-form-id="<%= form_id %>"></li>
<% end %>
Ratings controller is:
def create
end
def update
end
def rating_params
params.require(:rating).permit(:stars)
end
Migration file is:
create_table :ratings do |t|
t.integer :stars, :default => 0
t.references :store
t.references :user
end
From the comments, the error seems to be here:
#hotel.ratings.find_or_create_by_user_id user.id
--
user_id
The problem is your show view doesn't have access to a local variable called user
This variable should either be defined in the controller (which would mean it has to be an #instance variable, or should be a helper (such as current_user.id)
The fix should therefore be as follows:
<% user_id = user_signed_in? ? current_user.id : "-1" %>
<%= form_for #hotel.ratings.find_or_create_by_user_id user_id ...
This should get it working for you with the code you have provided. As you've not provided the new action from the controller, I don't know whether the supporting structure for the code will be correct or not.
After some search on find_or_create_by, I changed line with 'form_for' into
<%= form_for #hotel.ratings.find_or_create_by(user_id: user_id)
That solved the issue!
Thanks to all for your support!
Seems so simple, but this one makes me crazy by now:
I got a Topics table, which has an user_id that is written during the creation of a new topic, and comes from the User table.
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
has_many :topics
has_many :comments
validates_presence_of :password, :on => :create
validates_uniqueness_of :email
end
class Topic < ActiveRecord::Base
attr_accessible :active, :id, :opened_at, :title, :description
belongs_to :user
has_many :comments
end
Now the first thing I tried is writing the view like this:
<% #topics.each do |topic| %>
<%= topic.user.name %>
<% end %>
Which drops:
undefined method `name' for nil:NilClass
Then I tried another approach:
topics_controller.rb
def index
#topics=Topic.all
#author=User.find(#topics.user_id)
end
But this goes like:
undefined method `user_id' for #<Array:0x3c46fb0>
(If I hardcode any number instead of #topics.user_id then it shows the given user's name properly).
Any help is appreciated.
PS.: This is the way I save the Topic:
def create
#topic = Topic.new(params[:topic])
#topic.active = true
#topic.user_id = session[:user_id]
if #topic.save
redirect_to topics_url
flash[:notice] = 'Success!'
else
render "new"
end
end
I guess the association is OK, because when I put
<%=h topic.user_id %>
then it shows the proper IDs. It's just that I cannot translate the ID to the user name that is stored in the User table.
Use the following in case topic.user is nil:
<% #topics.each do |topic| %>
<%= topic.user.try(:name) %>
<% end %>
You should also remove #author=User.find(#topics.user_id). This will raise an error because #topic is a collection of all the Topic instances and not a single instance (like Topic.all.first as an example).
You are using #topics.user_id, which is where things are going wrong; you are trying to call user_id on an array instead of a single Topic object. Use #pluck:
Controller Code:
#topics = Topic.all
#topics_ids = Topic.pluck(:user_id).compact #will remove nils
#authors = User.find(#topics_ids)
View Code:
<b>All Topics: </b>
<% #topics.each do |topic| %>
<%= topic.user.name %>
<% end %>
<b>All Authors: </b>
<% #authors.each do |author| %>
<%= author.name %>
<% end %>