I'm creating my own blog on Rails with posts and users. I need to show all posts from specific author when I click on him (here the concept:link). What should I do for this?
Please say what extra information or code should I add
users_controller:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#posts = #user.posts
end
end
posts_controller:
class PostsController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index]
# GET /posts
# GET /posts.json
def index
#posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
# GET /posts/1
# GET /posts/1.json
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
# GET /posts/new
# GET /posts/new.json
def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #post }
end
end
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.json
def create
##post = Post.new(params[:post])
#post = current_user.posts.build(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render json: #post, status: :created, location: #post }
else
format.html { render action: "new" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# PUT /posts/1
# PUT /posts/1.json
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
end
user model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
has_many :posts, :dependent => :destroy
validates :fullname, :presence => true, :uniqueness => true
validates :password, :presence => true
validates :email, :presence => true, :uniqueness => true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :fullname
end
post model:
class Post < ActiveRecord::Base
attr_accessible :text, :title
validates :user_id, :presence => true
validates :title, :presence => true
validates :text, :presence => true
belongs_to :user
has_many :comments
end
This is a fairly straight forward use of Ruby on Rails. I recommend reading Active Record Basics to get up to speed.
First, you should have a belongs_to relationship between Posts and Users that looks like this:
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
end
This adds a .posts method to the User instance and a .user method to the Post instance.
Then you have to make a decision about how you want the URL structure of your application to work. Here are a few options from the top of my head:
/posts?user=:user_id
/posts/by/:user_id
/users/:id/posts
Given the relationship between a User and their Posts, my recommendation (and I believe the general "Rails Way") would be #3. So, let's add the routes to config/routes.rb:
The short way to create JUST that route:
get 'users/:id/posts' => 'users#posts', :as => :user_posts
The long way to create the route based on resources:
resources :users do
member do
get :posts
end
end
Both approaches will provide a helper method called user_posts_path and one called user_posts_url which can be used in your view to link to the list of user posts using the link_to helper method:
<%= link_to post.user.name, user_posts_path(post.user) %>
Now, you have to add the controller action in app/controllers/users_controller.rb:
class UsersController < ActionController::Base
def posts
#user = User.find(params[:id])
#posts = #user.posts
end
end
and then add your HTML/ERB code to app/views/users/posts.html.erb
<% #posts.each do |post| %>
<%= post.inspect %>
<% end %>
That should give you the basic ability to show a user's posts. You can enhance it by reusing a post partial or some other nice shortcuts, but I'll leave that as an exercise for you to figure out.
You need 2 models: User and Post. There is a relation between them: User HAS MANY posts, post BELONGS TO user. To create this relation in a database you should add user_id column to posts table. To do this simply run the following command:
rails generate migration AddUserIdToPosts user_id: integer
Don't forget to run rake db:migrate after that
To create association between models add to the User model:
has_many :posts, dependent: :destroy
And to Post model:
belongs_to :user
Now you can use 'user' method on post and 'posts' method on user. For example in show action of users controller:
#user = User.find(params[:id])
#posts = #user.posts
This links will help you:
http://guides.rubyonrails.org/association_basics.html
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Related
So I have a post.rb model where I can display who wrote the post in the show post view, and that works wonderfully.
However, I've been trying to add comments, which belong to a post, and also belong to a user.
I can't seem to get all the comments or the names of the users who posted the coments to show up in the post show.html.erb.
I guess my question is, how do I integrate controller and model information in between controller and model views? I know if it exists in the model and controller and view of the same type, it is accessible, but cross-linking or sharing controller model information is hard for me.
I want to be able to display the user that made the comment, along with the body of the comment in the show view of the post, and not the comment.
comment.rb
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
validates :body, :presence => true
end
post.rb
class Post < ApplicationRecord
belongs_to :user, optional: true
validates :user, presence: true;
validates :title, presence: true,
length: { minimum: 5}
extend FriendlyId
friendly_id :title, use: :slugged
validates :title, :slug, presence: true
# comments
has_many :comments
end
user.rb
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :first_name, presence: true
validates :last_name, presence: true
validates :school_name, presence: true, inclusion: { in: %w(Harvard Yale),
message: "%{value} is not a valid choice" }
validates :graduation_year, presence: true, inclusion: { in: %w(2017 2018 2019 2020 2021 2022 2023),
message: "%{value} is not a valid choice currently" }
def superadmin?
self.role.name == "Superadmin"
end
end
comments_controller.rb
class CommentsController < ApplicationController
before_action :authenticate_user!
# GET /posts/:post_id/comments
# GET /posts/:post_id/comments.xml
def index
#1st you retrieve the post thanks to params[:post_id]
post = Post.friendly.find(params[:post_id])
#2nd you get all the comments of this post
#comments = post.comments
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #comments }
end
end
# GET /posts/:post_id/comments/:id
# GET /comments/:id.xml
def show
#1st you retrieve the post thanks to params[:post_id]
post = Post.friendly.find(params[:post_id])
#2nd you retrieve the comment thanks to params[:id]
#comment = post.comments.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #comment }
end
end
# GET /posts/:post_id/comments/new
# GET /posts/:post_id/comments/new.xml
def new
#1st you retrieve the post thanks to params[:post_id]
post = Post.friendly.find(params[:post_id])
#2nd you build a new one
#comment = post.comments.new(params[:comment])
#comment.user = current_user
end
# GET /posts/:post_id/comments/:id/edit
def edit
#1st you retrieve the post thanks to params[:post_id]
post = Post.friendly.find(params[:post_id])
#2nd you retrieve the comment thanks to params[:id]
#comment = post.comments.find(comment_params[:id])
end
# POST /posts/:post_id/comments
# POST /posts/:post_id/comments.xml
def create
#1st you retrieve the post thanks to params[:post_id]
post = Post.friendly.find(params[:post_id])
#2nd you create the comment with arguments in params[:comment]
#comment = post.comments.create(comment_params)
#comment.user = current_user
respond_to do |format|
if #comment.save
#1st argument of redirect_to is an array, in order to build the correct route to the nested resource comment
format.html { redirect_to([#comment.post, #comment], :notice => 'Comment was successfully created.') }
#the key :location is associated to an array in order to build the correct route to the nested resource comment
format.xml { render :xml => #comment, :status => :created, :location => [#comment.post, #comment] }
else
format.html { render :action => "new" }
format.xml { render :xml => #comment.errors, :status => :unprocessable_entity }
end
end
end
In the show method of your post_controller, you'll need to do something like:
def show
#post = Post.find_by(id: params[:id]) # or however you find your post
....
end
Then, in your views/posts/_show.html.erb (if you're using erb), you'll do something like:
<% #post.comments.each do |comment| %>
... show some comment stuff
<%= comment.user.name %>
<% end %>
I'm very new to Ruby on Rails and I'm struggling with my scaffolded controller.
I made a nested resource where my comments are placed in posts.
class Post < ActiveRecord::Base
validates :name, :presence => true
validates :title, :presence => true, :length => { :minimum => 5 }
has_many :comments
end
class Comment < ActiveRecord::Base
validates :commenter, :presence => true
validates :body, :presence => true
belongs_to :post
end
A simplified version of the controller is
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
# omited new, index, show...
# POST /comments
# POST /comments.json
def create
post = Post.find(params[:post_id])
#comment = post.comments.create(params[:comment].permit(:name, :title, :context))
respond_to do |format|
if #comment.save
format.html { redirect_to([#comment.post, #comment], notice: 'Comment was successfully created.') }
format.json { render action: 'show', status: :created, location: #comment }
else
format.html { render action: 'new' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /comments/1
# PATCH/PUT /comments/1.json
def update
post = Post.find(params[:post_id])
#comment = post.comments.find(params[:comment])
respond_to do |format|
if #comment.update(comment_params)
format.html { redirect_to #comment, notice: 'Comment was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
#comment = Comment.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comment_params
params.require(:comment).permit(:commenter, :body, :post)
end
end
When i filled in the form, i get this exception:
2 errors prohibited this comment from being saved:
Commenter can't be blank
Body can't be blank
I tried this guide but i think it isn't 100% compatible with Rails 4.
You are reading from params attributes for a Post (:name, :title, :context), but you need to read Comments attributes (:commenter, :body)
replace this:
#comment = post.comments.create(params[:comment].permit(:name, :title, :context))
with
#comment = post.comments.create(params[:comment].permit(:commenter, :body))
or, much better, with:
#comment = post.comments.create(comment_params)
It appears that you are explicitly permitting different set of attributes in your create action.
You should update your create action to use comment_params like you've done in update action. The reason being your Comment is definitely expecting Commenter and Body and not :name, :title, :context which you've permitted in your create action.
Update controller's create action as:
# POST /comments
# POST /comments.json
def create
...
#comment = post.comments.create(comment_params)
...
end
I have a page that has a list of referrals on it. I have a button on each of the referrals that is set to reply to the referral. I don't need any pop up or form to show except for a flash message to show the user has successfully replied to the referral and toggling a class on the button when a user replies. Upon replying to the referral, email(is index for the table) is passed, referralid is also passed to the reply table. I have tried many methods, but I'm getting nowhere with the controllers. I created proper associations on the models, but still getting nowhere in the controller logic to create a reply record for every reply. Here are my models:
Referral Model
class Referral < ActiveRecord::Base
attr_accessible :referraltype
belongs_to :user
validates :user_id, presence: true
has_many :replies
def nil_zero?
self.nil? || self == 0
end
end
User Model
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :provider, :uid, :image
has_attached_file :image, styles: { medium: "320x320>", thumb: "50x50" }
has_many :referrals
has_many :replies
end
Replies Controller
class RepliesController < ApplicationController
end
Reply Model
class Reply < ActiveRecord::Base
belongs_to :user
belongs_to :referral
end
Referrals Controller
class ReferralsController < ApplicationController
before_filter :authenticate_user!
def reply_to_referral
#referral = Referral.find(params[:referral_id])
#replier_id = params[:replier_id]
#reply = #referral.replies.create(replier_id: #replier_id)
flash[:success] = "Referral reply sent."
redirect_to root_path
end
# GET /referrals
# GET /referrals.json
def index
#referrals = Referral.order("created_at desc")
#referrals
respond_to do |format|
format.html # index.html.erb
format.json { render json: #referrals }
end
end
# GET /referrals/1
# GET /referrals/1.json
def show
#referral = Referral.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #referral }
end
end
# GET /referrals/new
# GET /referrals/new.json
def new
#referral = current_user.referrals.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #referral }
end
end
# GET /referrals/1/edit
def edit
#referral = current_user.referrals.find(params[:id])
end
# POST /referrals
# POST /referrals.json
def create
#referral = current_user.referrals.new(params[:referral])
respond_to do |format|
if #referral.save
format.html { redirect_to #referral, notice: 'Referral was successfully created.' }
format.json { render json: #referral, status: :created, location: #referral }
else
format.html { render action: "new" }
format.json { render json: #referral.errors, status: :unprocessable_entity }
end
end
end
# PUT /referrals/1
# PUT /referrals/1.json
def update
#referral = current_user.referrals.find(params[:id])
respond_to do |format|
if #referral.update_attributes(params[:referral])
format.html { redirect_to #referral, notice: 'Referral was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #referral.errors, status: :unprocessable_entity }
end
end
end
# DELETE /referrals/1
# DELETE /referrals/1.json
def destroy
#referral = current_user.referrals.find(params[:id])
#referral.destroy
respond_to do |format|
format.html { redirect_to referrals_url }
format.json { head :no_content }
end
end
end
Routes.rb
GemPort::Application.routes.draw do
resources :referrals do
resources :replies
member do
put "reply_to_referral"
end
end
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
root :to => 'pages#home'
get 'about' => 'pages#about'
end
Migration for the Replies table
class CreateReplies < ActiveRecord::Migration
def change
create_table :replies do |t|
t.references :user
t.references :referral
t.timestamps
end
add_index :replies, :user_id
add_index :replies, :referral_id
end
end
code on the _referral.html.haml partial that is giving the error:
= link_to '<i class="icon-ok icon-large pull-right icon-grey" rel="tooltip" title="Reply"> Reply</i>'.html_safe, reply_to_referral_path(referral_id: referral.id, replier_id: current_user.id)
I know this must be simple to do in the controller, I tried using a helper but got nowhere
Add your routes and controller and we can give you a better answer, but I'm guessing that this isn't working since you're passing an email to the route.
Emails have full stops (.) which can break your route unless you add constraints to the route.
Try changing your route to something like:
resources :referrals do
member do
put "reply_to_referral" # will give you referrals/:id/reply_to_referral
end
end
Now change your link to reply_to_referral_path(id: referral.id, email: current_user.email), this should come out as /referrals/32/reply_to_referral?email=user#email.com
Then in referrals controller:
def reply_to_referral
#referral = Referral.find(params[:id])
#email = params[:email]
# now make sure your referral_replies table has a column called 'email' and
# also one called 'referral_id', then you can do:
#referral_reply = #referral.referral_replies.create(email: #email)
flash[:success] = "Referral reply sent."
redirect_to # wherever required
end
You could do something similar by adding a constraint to the route, or by passing in the user's id instead of email and then querying the database.
To style the button you can then check if the referral has any replies:
<% if referral.referral_replies.any? %>
# add a CSS class
<% end %>
Hi I'm currently working on my first project, and am trying to build the functionality first before doing the login/sessions. I'm trying to create a picture album website, where users have many albums (that contain many pictures), and album access is shared among friends. However, I'm noticing that after my albums#create
http://localhost:3000/users/18/albums/new (no problem here)
I am redirected to albums#show:
http://localhost:3000/albums/20 (problem!!)
shouldn't there be a user_id in the URL as well?? Or does it not have a user_id attached to the URL because it belongs to multiple users? Here are my routes:
Pholder::Application.routes.draw do
resources :users do
resources :albums
end
resources :albums do
resources :pictures
end
root :to => "users#index"
Here are my models in case:
user model
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email, :name, :password, :password_confirmation
validates_presence_of :password, :on => :create
validates_format_of :name, :with => /[A-Za-z]+/, :on => :create
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
validates_length_of :password, :minimum => 5, :on => :create
has_many :user_albums
has_many :albums, :through => :user_albums
accepts_nested_attributes_for :albums
end
album model
class Album < ActiveRecord::Base
attr_accessible :avatar, :name, :description
has_many :user_albums
has_many :users, :through => :user_albums
has_many :photos
end
photo album
class Photo < ActiveRecord::Base
belongs_to :album
end
albums controller
class AlbumsController < ApplicationController
def index
#albums = Albums.all
respond_to do |format|
format.html
format.json { render json: #albums }
end
end
def show
#albums = Album.all
#album = Album.find(params[:id])
#photo = Photo.new
end
def update
end
def edit
end
def create
# #user = User.find(params[:albums][:user_id])
# #users = User.all
#album = Album.new(params[:album])
# #album.user_id = #user.id
respond_to do |format|
if #album.save
format.html { redirect_to #album, notice: 'Album was successfully created.' }
format.json { render json: #album, status: :created, location: #album}
else
format.html { render action: "new" }
format.json { render json: #album.errors, status: :unprocessable_entity }
end
end
end
def new
#user = User.find(params[:user_id])
#album = Album.new
end
def destroy
end
end
Let me know if you need any other files.
The line redirect_to #album makes you redirect to the show action of the #album in question.
Changing this piece of code to something like redirect_to users_path will make the app redirect to the index action of users_controller and so on.
It depends on whatever behavior you want after the save.
Reading this should be helpful too: http://guides.rubyonrails.org/routing.html
I am using Devise and am trying to allow each User to create 1 Profile. I am able to send the the newly registered User to the page where they can create a Profile, but once the User logs out and back in it will not go to the Profile Show page.
In other words-
I can sign up a new User and send the User to the Create Profile page, then I can create a Profile with the new User(I am not sure the Profile is saving correctly)... After I log out and sign in I recieved the error:
ActiveRecord::RecordNotFound in ProfilesController#show
Couldn't find Profile without an ID
I would like the User to be sent to their Profile Show page...
Any thoughts on the issue?
The code (sorted by files) is below…
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
has_one :profile
end
profile.rb
class Profile < ActiveRecord::Base
attr_accessible :first_name, :last_name
belongs_to :user
end
profiles_controller.rb
class ProfilesController < ApplicationController
# GET /profiles
# GET /profiles.json
def index
#profiles = Profile.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #profiles }
end
end
# GET /profiles/1
# GET /profiles/1.json
def show
#profile = Profile.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #profile }
end
end
# GET /profiles/new
# GET /profiles/new.json
def new
#profile = Profile.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #profile }
end
end
# GET /profiles/1/edit
def edit
#profile = Profile.find(params[:id])
end
# POST /profiles
# POST /profiles.json
def create
#profile = Profile.new(params[:profile])
respond_to do |format|
if #profile.save
format.html { redirect_to #profile, notice: 'Profile was successfully created.' }
format.json { render json: #profile, status: :created, location: #profile }
else
format.html { render action: "new" }
format.json { render json: #profile.errors, status: :unprocessable_entity }
end
end
end
# PUT /profiles/1
# PUT /profiles/1.json
def update
#profile = Profile.find(params[:id])
respond_to do |format|
if #profile.update_attributes(params[:profile])
format.html { redirect_to #profile, notice: 'Profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #profile.errors, status: :unprocessable_entity }
end
end
end
# DELETE /profiles/1
# DELETE /profiles/1.json
def destroy
#profile = Profile.find(params[:id])
#profile.destroy
respond_to do |format|
format.html { redirect_to profiles_url }
format.json { head :no_content }
end
end
end
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
request.env['omniauth.origin'] || stored_location_for(resource) || new_profile_path
end
end
application_controller.rb
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
request.env['omniauth.origin'] || stored_location_for(resource) || show_path(resource.profile)
end
end
routes.rb
BaseApp::Application.routes.draw do
resources :profiles
get "users/show"
devise_for :users, :controllers => { :registrations => "registrations" }
resources :users
match '/show', to: 'profiles#show'
match '/signup', to: 'users#new'
root to: 'static_pages#home'
match '/', to: 'static_pages#home'
…
end
In your controller you use the following code #profile = Profile.find(params[:id]). When signing in params[:id] must be nil.
It's not nil when you redirect after creating because you send in an id here redirect_to #profile. That translates to redirect_to profile_path(#profile). When you use the /match path there is no id.
So one solution would be to use the helper current_user in the ProfileController's show action. Replace #profile = Profile.find(params[:id]) with #profile = current_user.profile. That might change your desired functionality as it will require a user to be signed in. This will keep the math path (/show url). It works because it no long relies on an id.
You could alternatively change the show_path(resource.profile) to profile_path(resource.profile). That will use the resources profiles path with the url /profiles/:id instead of show/ you were possibly looking for.
With answer #Phil provide I solved another problem in my project. Thanks \o/
ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-linux]
Rails 4.0.0
And your case, I solved this way:
Add inverse_of: in user and profile model:
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile, inverse_of: :user
end
profile.rb
class Profile < ActiveRecord::Base
belongs_to :user, inverse_of: :profile
validates :first_name, :user_id, :presence => true
validates :gender, :inclusion => {:in => %w(M F)}
end
In your application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
# redirect user after login
def after_sign_in_path_for(resource)
unless current_user.profile.nil?
profiles_path
else
flash[:alert] = "Please complete your profile"
new_profile_path
end
end
# redirect after logout
def after_sign_out_path_for(resource_or_scope)
new_user_session_path
end
end
This works for me, I hope this helps