I am new to web development (I have been coding for about 6/7 months now) in hopes of building my own app. I have been reading and working of the Ruby on Rails tutorial by Michael Hartl. I am on the chapter 9 section deleting users and am stuck. Everything works for the most part except that the user link. It just doesn't show on the display page. The users and their avatars fine. I just can't figure out a way to get the user delete link to show. Below is the code for the user model, the user controller, the session controller where the code current user lives and the view.
Also, when ever i take out the if statement from the user partial the delete link shows. I am not sure whats wrong since I already defined admin, and the current_user.
I would be entirely grateful for any kind of guidance. My project is also on github if the information below isn't sufficient enough
https://github.com/krischery2150/Try2150-master/tree/updating-users
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def new
#user = User.new
end
def show
#user = User.find(params[:id])
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile Updated"
redirect_to #user
else
render 'edit'
end
end
def index
#users = User.paginate(page: params[:page])
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "Your profile was deleted"
redirect_to users_url
end
private
def user_params
params.require(:user).permit(:username, :email, :password,
:password_confirmation, :user_about_me,
:birthday, :avatar, :gender)
end
##Before filters method
# Confirms that a given user is logged in. Only when these conditions are met the user will
# be able to update or edit their page
def logged_in_user
unless logged_in?
store_location
flash[:danger]= "Please Log In"
redirect_to login_url
end
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
.
class User < ActiveRecord::Base
before_save {self.email = email.downcase}
attr_accessor :remember_token
has_attached_file :avatar, styles: { medium: "300x300>", thumb: "50x50>" }, default_url: "/images/:thumb/missing.png"
validates_attachment_content_type :avatar, content_type: /\Aimage\/.*\Z/
# this before_save is a callback method. What it does is before it saves the email
#address it calls back and transforms all the letters into lower case. Had to do the indexing
#in active record in order for the method to work
validates :username , presence: true, length: {maximum: 250}
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
#code that ensures that a user puts the right format for emails in signup
#fields
validates :email, presence: true, length:{maximum: 50},
format:{with: VALID_EMAIL_REGEX },
uniqueness:{ case_sensitive: false }
#rails still assumes that uniquess is true
#whether the user types CAMELcase or lowercase
validates :password, presence: true, length:{maximum: 50}, allow_nil: true
validates :user_about_me, presence: true
validates :birthday, presence:true
has_secure_password
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
## returns a random user token
def User.new_token
SecureRandom.urlsafe_base64
end
# Remember a given user to the database for use of persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
##returns true if given token matches the digest
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
def forget
update_attribute(:remember_digest, nil)
end
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
end
This is the code in the partial rendering on the index page where all users show up.
<div class="col-md-9 col-offset-3" id="index-profile">
<li class="users">
<div class="col-xs-3 profilepic-container">
<%= image_tag user.avatar.url %>
</div>
<%= link_to user.username, user %>
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</li>
The delete link in your partial has two conditions that are required to be true. The user must be an admin, and the profile must not be their own profile. So if the admin user is the only user, then no delete link will show up.
Try creating a second user and see if the delete link shows up for that user.
Related
I'm facing to a stupid problem. I have created a collection select which is creating elements into a join table "staffs_task" to reference an association between the model staff and task.
And now I would like two things: (1) a button delete this association (2) and a little bit of code for my model staffs_task to avoid duplication, so with the task_id and staff_id. And last info, task is a model built by ranch
my code:
(the collection in new_task)
<%= select_tag "staffs_task", options_from_collection_for_select(#staffs, 'id', 'name') , :multiple => true %>
(task_controller)
skip_before_action :configure_sign_up_params
before_action :set_ranch
before_action :set_task, except: [:create]
def create
#task = #ranch.tasks.create(task_params)
#staffs = Staff.where(:id => params[:staffs_task])
#task.staffs << #staffs
if #task.save
#task.update(done: false)
#task.update(star: false)
flash[:success] = "The task was created "
else
flash[:success] = "The task was not created "
end
redirect_to #ranch
end
private
def task_params
params.require(:task).permit(:content, :deadline, :row_order, :date, :assigned_to)
end
def set_ranch
#ranch = Ranch.find(params[:ranch_id])
end
def set_task
#task = #ranch.tasks.find(params[:id])
end
So if you have any idea about one of this two things, your help would be welcome
Thanks in advance !!
Lets say you have the following many to many setup with a join model:
class Staff
has_many :assignments
has_many :tasks, through: :assignments
end
class Task
has_many :assignments
has_many :staff, through: :assignments
end
class Assignment
belongs_to :task
belongs_to :staff
end
Note that the plural of staff is staff - unless you are talking about the sticks carried by wizards.
ActiveRecord creates "magical" _ids setters for all has_many relationships. When used with a has_many through: relationship rails is smart enough to just remove the rows from the join table.
You can use this with the collection_select and collection_checkboxes methods:
<%= form_for([#task.ranch, #task]) do |f| %>
<%= f.collection_select(:staff_ids, Staff.all, :id, :name, multiple: true) %>
<% end %>
You would then set your controller up like so:
def create
#task = #ranch.tasks.new(task_params) do |t|
# this should really be done by setting default values
# for the DB columns
t.done = false
t.star = false
end
if #task.save
redirect_to #ranch, success: "The task was created"
else
render :new, error: "The task was not created"
end
end
private
def task_params
params.require(:task)
.permit(:content, :deadline, :row_order, :date, :assigned_to, staff_ids: [])
end
staff_ids: [] will allow an array of scalar values. Also not that .new and .create are not the same thing! You where saving the record 4 times if it was valid so the user has to wait for 4 expensive write queries when one will do.
I have a Rails app that has many Posts. Each Post has different parameters, two of these parameters are zagat_status and michelin_status. (it's a restaurant discovery website).
I'm trying to add 2 checkboxes that allow me to filter results for:
a) zagat_status .. so clicking the checkbox shows all Posts where zagat_status is "Yes"
b) michelin_status .. so clicking the checkbox shows all Posts where michelin_status = "1", "2", or "3"
Moreover, I want these two checkboxes to be able to work with each other. So if I click both, both filters apply simulatneously.
However, this is not working... how do I get the code below to work??
POST MODEL
class Post < ActiveRecord::Base
scope :zagat_status, -> (zagat_status) { where zagat_status: zagat_status }
scope :michelin_status, -> (michelin_status) { where michelin_status: michelin_status }
validates :name, presence: true
validates :city, presence: true
validates :address, presence: true
def self.search(query)
where("name like ? OR city like ? OR address like ?", "%#{query}%", "%#{query}%", "%#{query}%")
end
end
POST CONTROLLER
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
# GET /posts
# GET /posts.json
def index
#posts = Post.all
if params[:search]
#posts = Post.search(params[:search]).order("created_at DESC")
elsif params[:zagat_status].present?
#posts = Post.zagat_status(params[:zagat_status]).order("created_at DESC")
elsif params[:michelin_status].present?
#posts = Post.michelin_status(params[:michelin_status]).order("created_at DESC")
else
#posts = Post.all
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
#post = Post.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(
:name,
:michelin_status,
:zagat_status,
:address,
:city,
:cuisine,
:neighborhood,
:price_range,
:longitude,
:latitude)
end
end
INDEX.HTML.ERB FILE (for POSTS)
<div class="search-filter">
<form>
<span>Accolades</span>
<div class="accolades-panel">
<label>
Michelin Star(s) <dd>1,2,3 and bibs</dd>
<input type="checkbox" name="michelin_status" value="1">
</label>
<label>
Zagat<dd>rated</dd><input type="checkbox" name="zagat_status" value="Yes">
</label>
</div>
</form>
</div>
INDEX.JS.ERB FILE
Blank
INDEX.JSON.JBUILDER FILE
json.array!(#posts) do |post|
json.extract! post, :id, :name, :zagat_status, :michelin_status, :cuisine, :address, :city, :price_range, :longitude, :latitude
json.url post_url(post, format: :json)
end
Your code doesn't work because the results are returned immediately after one of the conditions in the controller are satisfied e.g. if params[:search] exists it will return:
#posts = Post.search(params[:search]).order("created_at DESC")
And the rest of the code won't be run, because it's inside elsif blocks.
Something like this should work:
#posts = Post.all
if params[:search]
#posts = #posts.search(params[:search]).order("created_at DESC")
end
if params[:zagat_status].present?
#posts = #posts.zagat_status(params[:zagat_status]).order("created_at DESC")
end
if params[:michelin_status].present?
#posts = #posts.michelin_status(params[:michelin_status]).order("created_at DESC")
end
This way we build the query based on the existence of parameters. However this is not the most elegant solution.
I would suggest you create a method in your model that would take in the parameters, and then create a query there based on the parameters.
I'm trying to attach user_id (so I can access user details) to comments that are already attached to a page called Park.
I have three tables set up up: Park, Comment, and User:
class Park < ActiveRecord::Base
has_many :comments
has_many :users
end
class Comment < ActiveRecord::Base
belongs_to :park
has_one :user
end
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_many :comments
validates_presence_of :username
validates :username, uniqueness: true
def admin?
admin
end
end
As you can see above, I'm using the Devise gem for users and authorizations.
Comments show on Park and so they are nested:
park_comment GET /parks/:park_id/comments/:id(.:format) comments#show
My comment controller is set up like this:
class CommentsController < ApplicationController
before_action :authenticate_user!, only: [:create]
before_action :authorize_admin!, only: [:delete]
def create
#park = Park.find(params[:park_id])
#comment = #park.comments.create(comment_params)
redirect_to park_path(#park)
end
def destroy
#park = Park.find(params[:park_id])
#comment = #park.comments.find(params[:id])
#comment.destroy
redirect_to park_path(#park)
end
private
def comment_params
params.require(:comment).permit(:comment, :user_id, :park_id)
end
end
I've been able to oscillate from park_id to user_id, but the trade off has consistently been to receive a big fat nil on the other term.
I just tried to add a hidden field to my comment form
<%= form_for([#park, #park.comments.build]) do |f| %>
<p>
<%= f.label :comment %><br>
<%= f.text_area :comment %>
<%= hidden_field_tag(:user_id, current_user.id) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
But that didn't seem to yield any results.
I had tried some ActiveRecord joining work, but it didn't want to latch on to my create so I gave that up. Having spent a while on this now, I'm convinced there's an easy solution I'm just not seeing. Ideas?
Your hidden_field_tag isn't working. It'll create a separate param, not one nested under the param key that you want.
My suggestion would be to remove the hidden_field altogether and specify the current_user in the controller.
class CommentsController < ApplicationController
def create
#park = Park.find(params[:park_id])
#comment = #park.
comments.
create(
comment_params.
merge(user_id: current_user.id) # adding here, on creation
# or merge(user: current_user)
)
redirect_to park_path(#park)
end
def comment_params
params.
require(:comment).
permit(:comment)
end
end
Why? Because otherwise a user can potentially change the value of that hidden_field and you'll be incorrectly storing the comment's user_id value.
I'm not sure if I understood you correctly, but looking at your code I can see some problems.
first of all, you create a hidden field but not using the form helper. That's why your user_id is not visible in your comment_params, causing your
big fat nil
:) .
Try to change
<%= hidden_field_tag(:user_id, current_user.id) %>
to
<%= f.hidden_field(:user_id, :value => current_user.id) %>
But a better Idea for this is to just remove the hidden field and change your controller to
def create
#park = Park.find(params[:park_id])
#comment = #park.comments.new(comment_params)
#comment.user = current_user
if #comment.save
redirect_to park_path(#park)
else
...
end
end
You can now remove your :user_id from your comment_params like this:
params.require(:comment).permit(:comment, :park_id)
Actually, you don't even need :park_id in there
I have a simple post and user app where users submit and upvote posts. I have two models, post and user, and right now I'm working with the post model to add "upvoting" functionality.
Each post has an :id, :upvote and :users_voted_by attribute.
Each user has a :username, :posts_voted_on and good_karma attribute
When a user clicks the upvote button on a post, I need to make sure the current user isn't in the :users_voted_by column for that post, if not, add 1 to the :upvote attribute of that post. This part is done.
But I also need to add the post :id into the current user's :posts_voted_on field and add 1 to the submitter's :good_karma field.
Post model:
class Post < ActiveRecord::Base
attr_accessible :comment_count, :downvote, :id, :text, :title, :upvote, :url, :user, :users_voted_by
end
User model:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :username, :posts_voted_on, :good_karma, :bad_karma
attr_accessor :password
before_save :encrypt_password
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email
validates_uniqueness_of :email
validates_presence_of :username
validates_uniqueness_of :username
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
end
My upvote method is within posts_controller:
def upvote
#post = Post.find(params[:post_id])
respond_to do |format|
if #post.users_voted_by.index(current_user.username) == nil && #post.update_attributes(:upvote => #post.upvote + 1, :users_voted_by => #post.users_voted_by + ',' + current_user.username)
format.html { redirect_to #post }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
The 5th line is where the magic is. Is makes sure the post I'm voting on doesn't have the current user in the :users_voted_by field. Then it updates the :upvote and :users_voted_by fields.
How can I add to this method (action?) so that it also updates the attributes of the voting user and the post submitter to store the increase in karma.
Guess a service object can help here, something like (not tested)
class Voter
def initialize(post, user)
#post = post
#user = user
end
def upvote
return false unless #post.users_voted_by.index(#user.username)
#post.upvote += 1
#post.users_voted_by = #post.users_voted_by + ',' + #user.username
#user.good_carma += 1
#post.save && #user.save
end
def downvote
...
end
end
Then controller will look like
def upvote
#post = Post.find(params[:post_id])
respond_to do |format|
if Voter.new(#post, current_user).upvote
format.html { redirect_to #post }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
NoMethodError in UsersController#show
undefined method `signed_in?' for #<UsersController:0x5bf3980>
Rails.root: C:/test_app
Application Trace | Framework Trace | Full Trace
app/controllers/users_controller.rb:91:in `signed_in_user'
Request
Parameters:
{"id"=>"1"}
Show session dump
Show env dump
Response
Headers:
None
In Users_Controller
def signed_in_user
redirect_to signin_path, notice: "Please Sign In." unless signed_in?
end
In SessionsController
def signed_in?
!current_user.nil?
end
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
self.current_user = user
end
def sign_out
cookies.delete(:remember_token)
self.current_user = nil
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
def signed_in?
!current_user.nil?
end
def create
user = User.authenticate(params[:session][:email],
params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination."
#title = "Sign In"
render 'new'
else
sign_in user
flash.now[:error] = "Welcome, #{user.name}"
render 'AdCon'
end
end
def destroy
sign_out
redirect_to root_path
end
private
def user_from_remember_token
User.authenticate_with_salt(*remember_token)
end
def remember_token
cookies.signed[:remember_token] || [nil,nil]
end
end
*EDIT:*************************************************
I'm using the tutorial on:
http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users # Listing 9.12
Listing 9.12. Adding a signed_in_user before filter.
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_filter :signed_in_user, only: [:edit, :update]
.
.
.
private
def signed_in_user
redirect_to signin_path, notice: "Please sign in." unless signed_in?
end
end
When I included the helper in the SessionsController I received the message
undefined method `signed_in?' for #
Extracted source (around line #9):
<div>
<% if controller.signed_in? %> <----LINE 9
<%= link_to "Sign Out", signout_path, :method => :delete %>
<% else %>
<%= link_to "Sign IN" , signin_path %>
I included the Helper like this:
class SessionsController < ApplicationController
include SessionsHelper
I couldn't get this to work, so I copied the helper methods into SessionsController and the error went away.and now I'm having an issue with 9.12 where signed_in? is an unknown method. and it makes sense because signed_in? is in SessionsController via a helper. can the UserController access that function. I'm new to rails and confused.
thanks for all the feedback
EDIT:******************
Here is the ApplicationController
class ApplicationController < ActionController::Base
protect_from_forgery
include ActionView::Helpers::SessionsHelper
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
You're declaring your shared methods in the wrong place. signed_in? should be defined inside your ApplicationController, which is the shared base class for all your other controllers. There is, in essence, no way for you to do what you're trying to do. The UsersController can't access your SessionController's methods, nor should it be able to. That isn't how controllers work.
current_user, current_user=, and signed_in? all belong in your ApplicationController, not your SessionsController, because they're shared methods meant to be used by all your controllers which inherit from ApplicationController.
I figured out, I had a sessionhelper file from another project open and I was editing that one instead of the one associated with my current project. Thanks for the help.