Limit users to create only one profile with Devise in Rails - ruby-on-rails

I have a user model and profile model using devise.
user has_one profile
profile belongs_to user
How can I throw an error if a user that already has a profile associate to them when they try to create another profile.
so if a user goes to example.com/profiles/new it would throw the error

Well you could do something like that:
profiles_controller.rb
def new
if current_user.profile.empty?
# create profil for user
else
# raise error which doesn't make sense or redirect like
redirect_to user_profile_path
end
end

#auL5agoi answer doesn't prevent someone from accessing the create action though. You want to run the check on both actions.
def ProfilesController < ApplicationController
before_action :check_profile_presence, only: [:new, :create]
def new
end
def create
end
private
def check_profile_presence
redirect_to user_profile_path if current_user.profile.exists?
end
end
http://guides.rubyonrails.org/action_controller_overview.html#filters

The best practice is to change your model! This will prevent problems in your db ... add to your model/profile.rb.
class Profile < ApplicationRecord
belongs_to :user
validates_uniqueness_of :user_id
#[... other codes...]
end

Related

Rolify Gem: User has to have at least one role

How can I validate presence of at least one role for a User using rolify gem? I tried validating presence of roles in User.rb as per below, but it does not work.
Bonus: Is it possible not to permit admin user take off his own Admin role?
User.rb:
class User < ApplicationRecord
rolify
validates :roles, presence: true
end
Edit Form:
= form_for #user do |f|
- Role.all.each do |role|
= check_box_tag "user[role_ids][]", role.id, #user.role_ids.include?(role.id)
= role.name
= f.submit
Controller:
class UsersController < ApplicationController
before_action :set_user, only: [:edit, :update, :destroy]
def edit
authorize #user
end
def update
authorize #user
if #user.update(user_params)
redirect_to users_path
else
render :edit
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit({role_ids: []})
end
end
When the user has 1+ roles it works ok, but if I take away all the roles it gives an error:
You can create a custom validation to requires that the user has at least one role:
class User < ActiveRecord::Base
rolify
validate :must_have_a_role
private
def must_have_a_role
errors.add(:roles, "must have at least one") unless roles.any?
end
end
The presence validation is really only intended for attributes and not m2m associations.
Is it possible not to permit admin user take off his own Admin role?
Its possible but will be quite complex since Rolify uses a has_and_belongs_to_many assocation and not has_many through: which would let you use association callbacks.

Only allow users to create one review per movie

I have my app setup where users can write reviews for a movie. What I'd like to do is limit the user to create only one review per movie. I've managed to accomplish this in my reviews controller as so:
class ReviewsController < ApplicationController
before_action :has_reviewed, only [:new]
....
def has_reviewed?
if Review.where(user_id: current_user.id, movie_id: #movie.id).any?
redirect_to movie_reviews_path
flash[:notice] = "You've already written a review for this movie."
end
end
end
Where I'm now having trouble is translating this same logic into my index view template with the helper methods of Devise and CanCanCan at my disposal.
<% if user_signed_in? && ... %> # current_user has already created a review for this movie
<%= link_to "Edit Review", edit_movie_review_path(#movie, review) %>
<% else %>
<%= link_to "Write a Review", new_movie_review_path %>
<% end %>
Also: Is there any way to improve the lookup in my has_reviewed? method? I feel like there's a better way to write it but can't determine the most appropriate fix.
Why not use a validation:
#app/models/review.rb
class Review < ActiveRecord::Base
validates :movie_id, uniqueness: { scope: :user_id, message: "You've reviewed this movie!" }
end
This is considering your review model belongs_to :movie
You could also use an ActiveRecord callback:
#app/models/review.rb
class Review < ActiveRecord::Base
before_create :has_review?
belongs_to :user, inverse_of: :reviews
belongs_to :movie
def has_review?
return if Review.exists?(user: user, movie_id: movie_id)
end
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :reviews, inverse_of: :user
end
Is there any way to improve the lookup in my has_reviewed? method?
def has_reviewed?
redirect_to album_reviews_path, notice: "You've already written a review for this album." if current_user.reviews.exists?(movie: #movie)
end
Why not make a has_reviewed? method on your User class?
e.g.
def has_reviewed?(reviewable)
# query in here
end
Then you should be able use that just fine in your controller and your views.
You will want to do this for both new and create. Otherwise a savvy user would be able to run a post that would get past your new action.
I would put the link_to in either a helper or a presenter object. It would generally look like this.
def create_or_edit_review_path(movie, current_user)
return '' if current_user.blank?
if current_user.review.present?
#Generate review edit link
else
#generate new link
end
end
After that in all of your views it would just be
<%= create_or_edit_review_path(#movie, current_user) %>
Then in your controller for both new and create you could do either a before action or just redirect on each.
before_action :enforce_single_review, only: [:create, :new]
def enforce_single_review
if current_user.review.present?
redirect_to review_path(current_user.review)
end
end
Here's what I came up with:
I created an instance method to retrieve a user's movie review using the find_by method on the Review model:
class User < ActiveRecord::Base
....
def movie_review(album)
Review.find_by(user_id: self, album_id: album)
end
end
This method also comes in handy when setting up my callback:
class ReviewsController < ApplicationController
before_action :limit_review, only: [:new, :create]
....
private
def limit_review
user_review = current_user.movie_review(#movie)
if user_review.present?
redirect_to edit_movie_review_path(#movie, user_review)
end
end
end
Created a helper method for showing the appropriate link to edit or create a review. Big thanks to Austio and his suggestion:
module ReviewsHelper
def create_or_edit_review_path(movie)
user_review = current_user.movie_review(movie) if user_signed_in?
if user_signed_in? && user_review.present?
link_to "Edit review", edit_movie_review_path(movie, user_review)
else
link_to "Write a review", new_movie_review_path
end
end
end
And at last this is how I call the helper in my view template(s):
....
<%= create_or_edit_review_path(#album) %>

How to Configure Routes of Scaffold Generator in Rails

I'm having trouble with configuring the model which is generated by scaffold in my Rails 4 application.
These are my models:
class Contact < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :contact
after_create :make_contact
def make_contact
create_contact(
:country => "USA",
:city => "Newyork"
)
end
end
As you can see, I'm creating a Profile model instance for each User when they registered for the site.
I generated User model with Devise GEM and generated Contact model with rails scaffold generator.
1) I want my Users to only update or view their profile. I want to prevent them to list all profiles, destroy their profiles or create a new profile. What is the best approach to do this?
2) I want my application to redirect automatically to the users related profile page when they visit /contacts route.
3) User can't be able to see other users profiles by changing the URL like /contacts/1, contacts/2 etc.
How can I do this?
Thanks.
User before_filter/before_action in your controller
def UsersController < ApplicationController
before_filter :restrict_user, :only => [:show, :edit, :update]
private
def restrict_user
redirect_to :root, :alert => "Not authorized" unless params[:id] = current_user.id
end
end
In your routes, you can specify only the actions that you want
resources :users, :only => [:new, :create, :edit, :update, :show] #index and destroy are not in the list
You can do the same in contacts controller too

Submit two forms at once with rails

Basically my idea is very simple - I want to create a new cart for each new user. The form itself is generated with scaffold and we're talking rails 4.0.1 here.
Is there a way to do that and if so - how? Maybe you can link me some live examples?
You do not need multiple forms to create multiple objects in Rails controller. Assuming that you have relationships like this:
class User < ActiveRecord::Base
has_many :carts #or has_one :cart
end
class Cart < ActiveRecord::Base
belongs_to :user
end
Then it's perfectly acceptable to do this:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new user_params
if #user.save
#user.carts.create # or #user.create_cart
redirect_to user_path
else
render action: :new
end
end
private
def user_params
params.require(:user).permit(...)
end
end
If the new user form happens to include some cart-specific details, then use fields_for to make them available in the form:
= form_for :user do |f|
... f.blah for user fields ...
= fields_for :cart do |cart_fld|
... cart_fld.blah for cart fields ...
and add cart_params to your controller.

Event booking application with ruby on rails

I have set up an event booking application with ruby on rails where I have users who can create events and the general public can book events. I am having problems implementing the booking feature. This is what I have done so far.
Created a Booking resource and associated it with the event model. The booking model contains the following attributes
Booker name
Booker Email
event_id
The goal is to "create a booking" for a current event. However I do not know how to pass the "current_event" parameter to the booking controller and I am also not sure how to define a "current_event".
Update your routes file like this (rails 4):
EventManagement::Application.routes.draw do
resources :events do
resources :bookings
end
end
This will give you a "nested route" -- the route to bookings is always "nested" under events. To create a new booking for an event, you'll use the new_event_booking_path(#event) route and to view a list of all the bookings for the event it's just event_bookings_path(#event). Each of these routes will put the event_id into the params hash.
class BookingsController < ApplicationController
before_filter :load_event
def index
#bookings = #event.bookings
end
def new
#booking = #event.bookings.build
end
def create
#booking = #event.bookings.build booking_params
if #booking.save
..
else
...
end
end
private
def load_event
#event = Event.find params[:event_id]
end
def bookings_params
params.require(:bookings).permit(:booker_name, :booker_email)
end
end
Actually I don't think you should have a Booking resource, but rather an Event resource and only a Booking model. The booking should happen in the events_controller, where you can easily specify the current #event.
Were I you, I would do the following.
# app/models/event.rb
class Event < ActiveRecord::Base
has_many :bookings
end
# /models/booking.rb
class Booking < ActiveRecord::Base
belongs_to :event
end
# app/controllers/events_controller.rb
class EventsController < ApplicationController
# POST /events/{:id}/book
def book_new_ticket
#event = Event.find(params[:id])
if #event.bookings.where(email: params[:email]).count > 0
redirect_to '/somewhere', alert: "THIS EMAIL HAS ALREADY BOOKED, YOU FOOL!"
else
Booking.create!(name: params[:name], email: params[:email], event_id: #event.id)
end
end
end
Haven't really run this code, but it's just a simulation.

Resources