Validation give me not correct result - ruby-on-rails

I wrote validation which check if role admin only one, role doesn't update on user. It's work, but then I have only one admin I can't update another users to admin. Wrote validation exception.
I try write math operators, like <= and <=> but it not working. I need what I can update user to admin if I have only one admin.
account.rb
class Account < ApplicationRecord
enum role: %i[user admin]
validate :admin_role
private
def admin_role
errors.add(:role, 'Must be one admin') if Account.where(role: :admin).count == 1
end
end
accounts_controller
class AccountsController < ApplicationController
def index
#accounts = Account.all.order(:id)
end
def update
#account = Account.find(params[:id])
if #account.valid?
#account.update(role: params[:role])
else
flash[:danger] = #account.errors.full_messages
end
redirect_to accounts_path
end
end
schema.rb
create_table "accounts", force: :cascade do |t|
t.integer "role", default: 0
end

Related

Need a validation for specific params of attribute

I need write validation for role parameter of accounts_controller. If records with role admin only one, this record cannot update attribute role to user.
This validation must close security bug. But I don't know how it write.
accounts_controller.rb
class AccountsController < ApplicationController
def index
#accounts = Account.all
end
def update
#account = Account.find(params[:id])
redirect_to accounts_path if account.update(role: params[:role])
end
end
account.rb
class Account < ApplicationRecord
enum role: [:user, :admin]
end
schema.rb
create_table "accounts", force: :cascade do |t|
t.integer "role", default: 0
end
I trying write something like this: validate :role, less_than_or_equal_to: 1 but it didn't works.
account.rb
validate :the_last_admin
protected
def the_last_admin
if Account.admin.count < 2
errors.add(:role, 'You are the last admin!')
end
end

How do I prevent model update from changing an attribute?

I have Account model with attribute role. Roles wrote with enum role: [:user, :admin]. I want that if left one account with role admin he can't update his role to user. I think need to write something like this: #account = Account.where(role: params[: role])... and then I don't know.
account.rb
class Account < ApplicationRecord
enum role: [:user, :admin]
end
accounts_controller.rb
class AccountsController < ApplicationController
def index
#accounts = Account.all
end
def update
#account = Account.find(params[:id])
redirect_to accounts_path if account.update(role: params[:role])
end
end
schema.rb
create_table "accounts", force: :cascade do |t|
t.integer "role", default: 0
end
I think what you want is a model callback before_update that acts like a validation.
class Account < ApplicationRecord
enum role: [:user, :admin]
before_update :insure_admin, if: -> { role == :admin && role_changed? }
private
def insure_admin
errors.add(:role, "admin cannot switch to regular user")
end
end
This should prevent the account.update(role: params[:role]) from returning true but you'll probably want to handle the error in your controller, something like:
class AccountsController < ApplicationController
def index
#accounts = Account.all
end
def update
#account = Account.find(params[:id])
if account.update(role: params[:role])
redirect_to accounts_path
else
redirect_to :back, flash: account.errors.full_messages
end
end
end
You might also want to add front end form validation to not allow the form to change role if the account is already persisted.

Ruby on Rails - how to handle money and create multiple bookings with one payment

I'm building an events app using RoR. Currently a User is only able to book and pay for one space at a time. I'm trying to create a MVC method/process that allows one user to be able to book multiple spaces and for that booking to be processed with one payment. So, somebody who wants to book 5 spaces at £10 each will type in the quantity they require and on the payment page they will see the total amount required (£50) and proceed to pay.
The code I've implemented so far simply isn't working. The issues centre on the right Model method to implement not only to do the multiplication (quantity * price) but also to handle the conversion of the £ symbol (string to float).
This is the Booking Model -
Booking.rb
class Booking < ActiveRecord::Base
belongs_to :event
belongs_to :user
def total_amount
#quantity.to_i * #price_currency.to_money
quantity.to_i * strip_currency(event.price)
end
private
def strip_currency(amount = '')
amount.to_s.gsub(/[^\D\.]/, '').to_f
end
end
And this is the Controller -
bookings_controller.rb
class BookingsController < ApplicationController
before_action :authenticate_user!
def new
# booking form
# I need to find the event that we're making a booking on
#event = Event.find(params[:event_id])
# and because the event "has_many :bookings"
#booking = #event.bookings.new(quantity: params[:quantity])
# which person is booking the event?
#booking.user = current_user
##booking.quantity = #booking.quantity
end
def create
# actually process the booking
#event = Event.find(params[:event_id])
#booking = #event.bookings.new(booking_params)
#booking.user = current_user
#price = price
#quantity = quantity
#total_amount = #booking.quantity.to_f * #event.price.to_f
Booking.transaction do
#event.reload
if #event.bookings.count > #event.number_of_spaces
flash[:warning] = "Sorry, this event is fully booked."
raise ActiveRecord::Rollback, "event is fully booked"
end
end
if #booking.save
# CHARGE THE USER WHO'S BOOKED
# #{} == puts a variable into a string
Stripe::Charge.create(amount: #event.price_pennies, currency: "gbp",
card: #booking.stripe_token, description: "Booking number #{#booking.id}")
flash[:success] = "Your place on our event has been booked"
redirect_to event_path(#event)
else
flash[:error] = "Payment unsuccessful"
render "new"
end
if #event.is_free?
#booking.save!
flash[:success] = "Your place on our event has been booked"
redirect_to event_path(#event)
end
end
#def total_amount
##total_amount = #booking.quantity * #event.price
#end
private
def booking_params
params.require(:booking).permit(:stripe_token, :quantity)
end
end
In the Event show view the user chooses the quantity they require here and then clicks on 'Book the Event'
events.show.html.erb
<form>
number of spaces:
<input type="number" placeholder="1" min="1" value="1">
</form>
<button><%= link_to "Book the Event", new_event_booking_path(#event) %></button>
This clicks through to the booking form/page which should show the total amount via the code below -
Booking.new.html.erb
<p>Total Amount <%= #booking.total_amount %></p>
However, this is what is showing -
How do I make this work? Should I be implementing methods in Events model rather than bookings or both?
This is my schema - do I need to amend anything here?
schema.rb
create_table "bookings", force: :cascade do |t|
t.integer "event_id"
t.integer "user_id"
t.string "stripe_token"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "quantity"
end
Relevant events schema -
t.integer "number_of_spaces"
t.integer "price"
t.boolean "is_free"
t.integer "price_cents", default: 0, null: false
t.integer "price_pennies", default: 0, null: false
t.string "price_currency", default: "GBP", null: false
t.boolean "happened", default: false
end
I am using the money-rails gem only. I am not using monetize - do I need the monetize gem to make this work or is there something I can do without using it?
Your main issue is on the domain modeling level.
You have an Event model (the product), and Booking model (the line item) that models a single reservation. What you are missing is a model that corresponds to a group of bookings. Lets call it - Order.
In following the Single Responsibility Principle (SRP) we also want a model that takes care of payments.
class User < ActiveRecord::Base
has_many :orders
def current_order
self.orders.unpayed.last
end
end
class Event < ActiveRecord::Base
has_many :bookings
has_many :orders, through: :bookings
end
class Booking < ActiveRecord::Base
belongs_to :event
belongs_to :order
end
class Order < ActiveRecord::Base
enum status: [:open, :payed, :confirmed] #etc
belongs_to :user
has_many :bookings
has_many :events, through: :bookings
has_many :payments
end
class Payment < ActiveRecord::Base
belongs_to :order
has_one :user, through: :order
end
Now you next issue is that you have shoved everything into your single BookingsController. Stop thinking about your application in terms of forms and think about it as a series of RESTful actions that a user can perform.
In this example creating orders is implicit. It also uses nested routes.
class BookingsController
before_action :set_event
# GET /events/:event_id/bookings/new
def new
#booking = #event.bookings.new
end
# POST /events/:event_id/bookings
def create
#order = current_user.current_order || current_user.order.create
#booking = #event.bookings.new(booking_params) do |b|
b.order = #order
end
if #booking.save
redirect_to #event, notice: 'Your reservation has been added to your order'
else
render :new
end
end
private
def set_event
#event = Event.find(params[:event_id])
end
def booking_params
params.require(:booking).permit(:a, :b, :c)
end
end
We will also need a controller for orders:
class OrdersController
# GET /orders
def index
#orders = current_user.orders
end
# GET /orders/:id
def show
#order = Order.find(params[:id])
end
end
In your layout you will want to add a link to the users current order. From that view the user should be able to complete the order by creating a payment.
class PaymentsController
before_action :set_order
# GET /orders/:order_id/payments
def index
#payments = #order.payments
end
# GET /orders/:order_id/payments/new
def new
#payment = #order.payments.new
end
# POST /orders/:order_id/payments
def create
#payment = #order.payments.new(payment_params)
if #payment.save
Stripe::Charge.create(amount: #booking.price_pennies, currency: "gbp",
card: #booking.stripe_token, description: "Booking number #{#booking.id}")
# #todo handle stripe failures!
# #todo update Order if payment is succesful.
else
flash[:error] = "Payment unsuccessful"
render :new
end
end
private
def set_event
#event = Event.find(params[:id])
end
def payment_params
params.require(:payment)
end
end
What about the pounds and pennies?
Right now thats not really your biggest concern. Dealing with numeric input from users is indeed a tricky task and using a library like monetize means you can spend your time on other matters.
And dealing with user input is not the models job anyways in MVC. It's the controllers.

Rails says that "role", a property I made doesn't exist

I clearly made the role for users (as you can see down below) but it says it doesn't exist. Help please? By the way, you can see how I'm hardcoding myself.
app/controllers/application-controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_user
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def require_user
redirect_to '/login' unless current_user
end
def require_admin
redirect_to '/' unless current_user.admin
end
end
User.create(first_name: "Johnny", last_name: "Appleseed", email: "j.appleseed#example", password: "MY AWESOME PASSWORD THAT NOBODY KNOWS", role: "admin")
db/migrate/20160109170743_create_users:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.string :password_digest
t.string :role, :default => "reader"
t.timestamps null: false
end
end
end
app/controllers/users-controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to '/'
else
redirect_to '/signup'
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :role)
end
end
Without knowing the specific error message, I can only speculate that your error is here:
def require_admin
redirect_to '/' unless current_user.admin
end
Regardless of the attributes you have in your model / db, you'll only get instance methods you've defined. You don't have admin in your User object, thus making current_user.admin invalid.
You'd need to use the following:
#app/models/user.rb
class User < ActiveRecord::Base
def admin?
self.role == "admin"
end
end
current_user.admin? #-> true / false
Whilst the question mark isn't required, it denotes the evaluation of an object's properties (true / false).
As an aside, you may want to look at adding an enum to your User model:
#app/models/user.rb
class User < ActiveRecord::Base
enum role: [:reader, :admin]
end
This will give you a series of instance & class methods to better help your logic:
#user = User.find params[:id]
if #user.admin?
...
#admins = User.admin
#-> collection of "admin" users
To do it, you'll need to change your role column from string to integer
I would suggest that you use boolean for the column role.
User.rb
def admin?
self.role == true
end
so you can do
redirect_to '/' unless current_user.admin?

How to make an admin user using sign up with google gem in rails 3

I am making an app that I want to only use google to log in (because I am using other google api and i want to sync account.) I also want admin to be able to make other user admin too.
I want to make some user admin, but if they are log in with google, how can i make this exception in controller?
Is it best to save specific user in database?
I'm so confuse! please help.
right now this is what i have for admin
omniauth for checking google login
class OmniauthCallbacksController < ApplicationController
def google_oauth2
if auth_details.info['email'].split("#")[1] == "company_name.net"
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash.notice = "Signed in Through Google!"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
flash.notice = "Please provide a password"
redirect_to new_user_registration_url
end
else
render :text => "Sorry this site is for company_name employees only"
end
end
end
migration for admin to user
class AddAdminToUser < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, :default => true
end
end
table for user roles
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.string :name
t.timestamps
end
end
end
HaveAndBelongToMany migration
class UsersHaveAndBelongToManyRoles < ActiveRecord::Migration
def self.up
create_table :roles_users, :id => false do |t|
t.references :role, :user
end
end
def self.down
drop_table :roles_users
end
end
First, I think you probably want to make new users default to NOT being admins, and only set that admin boolean to true if a current admin later makes them an admin.
To authenticate people, I suggest using the omniauth gem with one of the Google strategies. It will help validate their credentials against the user's Google account. Then you can store any information about that user relevant to your app, including whether they're an admin, in your own database, and use that information for authorization within your app. Hope that helps.

Resources