Devise CRUD validations - ruby-on-rails

Problem: I want to make password & password_confirmation fields validates presence:true for create action and no validation for update action
guest.rb:
class Guest < ActiveRecord::Base
devise :database_authenticatable, :recoverable, :rememberable, :trackable
validates :email, presence: true
end
My guests_controller.rb:
class GuestsController < ApplicationController
before_action :set_guest, only: [:show, :edit, :update]
def index
#guests = Guest.all
end
def show
#guest = Guest.find(params[:id])
end
def new
#guest = Guest.new
end
def edit
#guest = Guest.find(params[:id])
end
def create
respond_to do |format|
format.html do
#guest = Guest.new(guest_params)
if #guest.save
redirect_to guests_path, notice: 'Client was successfully created.'
else
render :new
end
end
end
end
def update
#guest = Guest.find(params[:id])
if #guest.update_attributes(guest_params)
sign_in(#guest, :bypass => true) if #guest == current_guest
redirect_to guests_path, notice: 'Client was successfully updated.'
else
render :edit
end
end
If I put validates :password, presence: true, it effects everything, whereas I need it only for create

From the Active Record Validations Guide:
The :on option lets you specify when the validation should happen. The default behavior for all the built-in validation helpers is to be run on save (both when you're creating a new record and when you're updating it). If you want to change it, you can use on: :create to run the validation only when a new record is created or on: :update to run the validation only when a record is updated.
So in your case you would use:
validates :email, presence: true, on: :create
I suggest you take a moment to sit down and read through the entire guide and the API documentation for validates.

Related

Trouble deploying pundit authorization in Rails 4.2.4 with devise

I am a junior developer working on my first web application for a customer, an electronic commerce portal using Rails 4.2.4, Devise and a pins scaffolding.
Devise is working great and anyone can signup and login and CRUD a pin.
Problem: Users cannot be given access to CUD as the pins contain live customer products that are for sale.
I am therfore trying to implement pundit so that users are purely RESTRICTED to read only and CANNOT create, update or destroy pins, Only the business owner can do so in an Admin capacity.
Currently, I am getting the error "Pundit::NotDefinedError in PinsController#new
1.) How can I add myself as the admin(is this through a migration?)
2) How can I get Pundit working.
class PinsController < ApplicationController
before_action :set_pin, only: [:show, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
def index
if params[:search].present? && !params[:search].nil?
#pins = Pin.where("description LIKE ?", "%#{params[:search]}%").paginate(:page => params[:page], :per_page => 15)
else
#pins = Pin.all.order("created_at DESC").paginate(:page => params[:page], :per_page => 15)
end
end
def show
end
def new
#pin = current_user.pins.build
authorize #pins
end
def edit
end
def create
#pin = current_user.pins.build(pin_params)
if #pin.save
redirect_to #pin, notice: 'Pin was successfully created.'
else
render :new
end
end
def update
if #pin.update(pin_params)
redirect_to #pin, notice: 'Pin was successfully updated.'
else
render :edit
end
end
def destroy
#pin.destroy
redirect_to pins_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_pin
#pin = Pin.find_by(id: params[:id])
end
def correct_user
#pin = current_user.pins.find_by(id: params[:id])
redirect_to pins_path, notice: "Not authorized to edit this pin" if #pin.nil?
end
# Never trust parameters from the scary internet, only allow the white list through.
def pin_params
params.require(:pin).permit(:description, :image)
end
end
Blockquote
class ApplicationPolicy
attr_reader :user, :pin
def initialize(user, pin)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
#user = user
#pin = pin
end
def index?
true
end
def show?
scope.where(:id => record.id).exists?
end
def create?
user.admin?
end
def new?
user.admin?
end
def update?
user.admin?
end
def edit?
update?
end
def destroy?
user.admin?
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
Blockquote
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
protect_from_forgery with: :exception
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :name
devise_parameter_sanitizer.for(:account_update) << :name
end
private
def user_not_authorized
flash[:warning] = "You are not authorized to perform this action."
redirect_to(request.referrer || root_path)
end
end
Blockquote
class Pin < ActiveRecord::Base
belongs_to :user
has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" }
validates_attachment_content_type :image, :content_type => ["image/jpg", "image/jpeg", "image/png"]
validates :image, presence: true
validates :description, presence: true
end
Blockquote
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 :pins, dependent: :destroy
validates :name, presence: true
end

before_validation method not working

I am a newB in rails and I am working on a project and wanted to remove trailing and leading white spaces from username and email. So I created a method in user model
class User < ActiveRecord::Base
include CarrierWave::MiniMagick
#removes the white spaces before validating the data
before_validation :strip_whitespace, :only => [:name,:email]
#data validations
validates :email, :presence =>true, :uniqueness => {case_sensitive: false}, :format => { :with=> /([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)/, :message => "please enter a valid e-mail" }
validates :name, :presence=>true
validates :password ,:presence =>true, :confirmation=> true #, :length =>{ :minimum=>6, :maximum=>30}, :format=>{:with=>/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,30}/}
#for the image
mount_uploader :image, ImageUploader
#for the password
has_secure_password
#associations
has_many :issues
has_many :comments
end
def strip_whitespace
self.email = self.email.squish
self.name = self.name.squish
end
when I enter user information in Users.create action, the leading and trailing spaces are removed, But when I login from my sessions controller, the leading and trailing spaces are not removed and hence it shows me an error.
Please help
Code for the users controller
class UsersController < ApplicationController
before_action :not_logged_in?, :only => [:new]
def new
#user=User.new
end
def create
#user = User.new(user_params)
if #user.save
sign_in #user
RegistrationsMailer.welcome_email(#user).deliver
flash[:success] = 'you are successfully registered'
redirect_to :controller => 'users' , :action => 'show', :id => #user.id
else
render 'new'
end
end
def show
#user=User.find(params[:id])
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to #user
else
render 'edit'
end
end
protected
def user_params
params.require(:user).permit(:name,:email,:password,:image)
end
end
code for sessions controller
class SessionsController < ApplicationController
def new
end
def create
#user = User.find_by_email(params[:sessions][:email])
if #user && #user.authenticate(params[:sessions][:password])
sign_in #user
redirect_to #user
else
flash.now[:error] = 'Invalid email or password'
render 'new'
end
end
def destroy
end
end
Please help
According to When does Validation happen?
The following methods trigger validations, and will save the object to the database only if the object is valid:
create
create!
save
save!
update
update!
When you are creating or destroying a new session, you don't actually call any of those methods on the User model so validations won't be called.
But since your database has the squished values, you need to modify your create action in the SessionsController to squish the passed in params.
def create
#user = User.find_by_email(params[:sessions][:email].squish)
if #user && #user.authenticate(params[:sessions][:password].squish)
sign_in #user
redirect_to #user
else
flash.now[:error] = 'Invalid email or password'
render 'new'
end
end

Can I edit all users as admin in devise with cancan

I have set up users with devise and each user can select a role. What I am trying to do is allow admins to be able to edit any user on the site if they have role admin. I currently have a UsersController setup like this:
class UsersController < ApplicationController
before_filter :authenticate_user!, only: [:index, :new, :edit, :update, :destroy]
skip_before_filter
def index
#users = User.order('created_at DESC').all
end
def show
#user = User.friendly.find(params[:id])
#users_authors = User.all_authors
end
# get authors index in here
def authors
end
def create
#user = User.create(user_params)
end
def edit
#user = User.find(params[:id])
end
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
def destroy
#user = User.find(params[:id])
#user.destroy
if #user.destroy
redirect_to users_url, notice: "User deleted."
end
end
private
def user_params
params.require(:user).permit(:avatar, :email, :name, :biography, :role_id, :book_id, :username, :password, :password_confirmation)
end
end
This is trying to create a CRUD to edit users which works but I need to be able to populate the forms in the users/edit view wityh the correct selected users details. I my devise controller I have this setup:
class Admin::UsersController < Admin::BaseController
helper_method :sort_column, :sort_direction
before_filter :find_user, :only => [:edit, :update, :show, :destroy]
def index
#q = User.search(params[:q])
#users = find_users
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to admin_users_path, :notice => "Successfully created user."
else
render :new
end
end
def show
end
def edit
#user = User.find(params[:id])
end
def update
if #user.update_attributes(user_params)
redirect_to admin_users_path, :notice => "Successfully updated user."
else
render :edit
end
end
def destroy
#user.destroy
redirect_to admin_users_path, :notice => "User deleted."
end
protected
def find_user
#user = User.find(params[:id])
end
def find_users
search_relation = #q.result
#users = search_relation.order(sort_column + " " + sort_direction).references(:user).page params[:page]
end
def sort_column
User.column_names.include?(params[:sort]) ? params[:sort] : "created_at"
end
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : "desc"
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:email,:username,:name,:biography,:role_id,:book_id,:role_name,:password,:password_confirmation,:encrypted_password,:reset_password_token,:reset_password_sent_at,:remember_created_at,:sign_in_count,:current_sign_in_at,:last_sign_in_at,:current_sign_in_ip,:last_sign_in_ip)
end
end
For clarity here is the user model:
class User < ActiveRecord::Base
belongs_to :role
has_many :books, dependent: :destroy
has_many :ideas, dependent: :destroy
accepts_nested_attributes_for :books
accepts_nested_attributes_for :ideas
def confirmation_required?
false
end
extend FriendlyId
friendly_id :username, use: [:slugged, :finders]
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_attached_file :avatar, styles: {
large: "600x450#",
medium: "250x250#",
small: "100x100#"
}, :default_url => "/images/:style/filler.png"
#validates_attachment_content_type :avatar, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
validates :avatar, :email, :username, :password, presence: true
def self.all_authors
User.select('users.id, users.username, users.role_id AS USER_ROLE')
.joins(:role).where(users: {role_id: '2'})
end
before_create :set_default_role
private
def set_default_role
self.role ||= Role.find_by_name('Admin')
end
end
In my routes I added a new route for users below the devise users resource as suggested on devise wiki like so:
devise_for :users, :path_prefix => 'my', :path_names => { :sign_up => "register" }
namespace :admin do
resources :users
end
Can anyone help with adding the ability of admins being able to edit all users here, I think its right but I cannot get the correct data into the forms in edit, it uses the current logged users details only.
A first draft for your ability.rb would be:
class Ability
include CanCan::Ability
def initialize(user)
# ...
if user.admin?
can :manage, User
end
# ...
end
end
And then in your user's controller remove the before_filter :find_user, :only => [:edit, :update, :show, :destroy] and related method, and use
load_and_authorize_resource :user
That would load the user from the URL and authorize! it using CanCan. You'll also need to handle the CanCan::AccessDenied exception for non-admin users visiting those pages, but that is another question that you can check in the CanCan docs.
When you visit admin_users_path routes you'll be able to CRUD them if you have the views ready and working.

How can I get all posts from a specific user

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

Connecting a User(devise) to their Profile

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

Resources