I have an index page that hides/shows different Authors by using a boolean. The problem that I am having is that signed in users can still access hidden authors and their books through the URL.
How can I prevent current users from navigating to hidden Authors and their corresponding Books through the URL? Is there a way to redirect them back to Authors Page if author is hidden?
Currently, I have used the Controllers & a boolean value to help hide/show Authors or Books from signed in users. Can someone please point me in the right direction. Here is my Code.
MODELS
class Author < ActiveRecord::Base
attr_accessible :name, :photo
has_many :books
end
class Book < ActiveRecord::Base
attr_accessible :author_id, :title, :photo
belongs_to :author
end
CONTROLLERS
class AuthorsController < ApplicationController
before_filter :signed_in_user, only: [:index]
before_filter :admin_user, only: [:edit, :update, :destroy, :new, :show]
respond_to :html, :js
###Only displays unhidden authors to non admin users.
def index
if current_user.admin?
#authors = Author.all(:order => "created_at")
else
#authors = Author.where(:display => true).all(:order => "created_at")
end
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to (root_path), notice: "Please sign in."
end
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
class BooksController < ApplicationController
before_filter :signed_in_user, only: [:index]
before_filter :admin_user, only: [:edit, :update, :destroy, :new, :show]
before_filter :get_author
respond_to :html, :js
def get_author
#author = Author.find(params[:author_id])
end
def index
#books = #author.books
end
private
def signed_in_user
unless signed_in?
store_location
redirect_to (root_path), notice: "Please sign in."
end
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
VIEWS
Authors index.html.erb
<% #authors.each do |author| %>
<%= link_to (image_tag author.photo(:medium)),
url_for(author_books_path(author)),
class: "img-rounded" %>
<% end %>
### How Can I prevent Users from accessing Hidden Author's Books (Index Page)
Books index.html.erb
<% #books.each do |book| %>
<%= image_tag book.photo(:medium) %>
<%= book.name %>
<% end %>
ROUTES
resources :authors do
resources :books
end
SCHEMA
create_table "authors", :force => true do |t|
t.string "name"
t.boolean "display", :default => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "photo_file_name"
t.string "photo_content_type"
t.integer "photo_file_size"
t.datetime "photo_updated_at"
end
create_table "books", :force => true do |t|
t.integer "author_id"
t.string "title"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "photo_file_name"
t.string "photo_content_type"
t.integer "photo_file_size"
t.datetime "photo_updated_at"
end
Use a proper authorization model, such as CanCan.
The key part (for CanCan) would be in authorizing based on user roles:
if user.role == admin
can :manage, :all
else
can :read, Author, display: true
end
There is a helpful RailsCast to step you through using CanCan for authorization.
Other options exist, such as Declarative Authorization, or you can roll your own.
Use a proper authorization model, such as CanCan.
Personally, I'm not sure I agree with this, at least given the scope of the issue you seem to be trying to solve. However, your comments in /app/views/books/index.html.erb seem to indicate you wanted to place some logic in your view file. Do NOT do this. Following proper MVC architecture, what you're attempting to do falls under the category of business logic. As such, the code that controls this should be in your controllers.
In your /app/controllers/book_controller.rb file, change the action for an Author's books page to redirect back to the author depending on the author's attributes. Something like this: (not sure what the exact path would be):
def index
# Redirect if author is set to hidden
if !#author.display
redirect_to author_path
else
#books = #author.books
end
end
in the Authors#show Controller, you can write for example --
Authors#Show
def show
#author = Author.find(params[:id])
redirect_to root_url unless #author.display
end
In this case, when a user visits any author's url, it will check to see if that author's display attribute is true or not, and redirect accordingly.
Related
Im making an application where an user can book a hour of training. I want to give the app the restriction of when a training has 24 users booked nobody cant book anymore (at least some user delete his book), my question is how can i implement this function MAXSLOT in training model and how i can put it to work
class BookingsController < ApplicationController
before_action :load_training, only: [:create]
def new
#booking = Booking.new
#training = Training.find(params[:training_id])
#booking.training_id
end
def create
#booking = #training.bookings.build(booking_params)
#booking.user = current_user
if #booking.save
flash[:success] = "Book created"
redirect_to trainings_path
else
render 'new'
end
end
def index
#bookings = Booking.all
end
def destroy
#booking = Booking.find(params[:id])
#booking.destroy
flash[:success] = "Book deleted"
redirect_to trainings_path
end
private
def booking_params
params.require(:booking).permit(:user_id, :training_id)
end
def load_training
#training = Training.find(params[:training_id])
end
end
Booking model:
class Booking < ApplicationRecord
belongs_to :user
belongs_to :training
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :training_id, presence: true
end
My routes.rb:
Rails.application.routes.draw do
root 'static_pages#home'
get '/signup', to: 'users#new'
get '/contact', to: 'static_pages#contact'
get '/about', to: 'static_pages#about'
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resources :trainings do
resources :bookings
end
resources :users
end
Training model:
class Training < ApplicationRecord
has_many :users, through: :bookings
has_many :bookings
end
Trainings controller:
class TrainingsController < ApplicationController
def show
#training = Training.find(params[:id])
end
def index
#trainings = Training.all
end
end
Index of training view:
<h1>Hours</h1>
<ul class="trainings">
<% #trainings.each do |training| %>
<li>
<%= link_to training.hour, training_path(training) %>
</li>
<% end %>
</ul>
Show of training view:
<div class="row">
<section>
<h1>
HOUR: <%= #training.hour %>
</h1>
</section>
<section>
<h1>
SLOTS: <%= #training.slots %>
</h1>
</section>
<center>
<%= render 'bookings/booking_form' if logged_in? %>
<%= render 'bookings/index_bookings' if logged_in? %>
</center>
This is my squema.rb:
create_table "bookings", force: :cascade do |t|
t.integer "user_id"
t.integer "training_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["training_id"], name: "index_bookings_on_training_id"
t.index ["user_id"], name: "index_bookings_on_user_id"
end
create_table "trainings", force: :cascade do |t|
t.integer "slots"
t.text "hour"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["hour"], name: "index_trainings_on_hour"
end
Thanks
There are a couple of approaches to this. I'd recommend something like this:
# Training
class Training < ApplicationRecord
has_many :users, through: :bookings
has_many :bookings
# Check if anymore bookings can be added
def can_book?
bookings.count < slots # Depending on the exact logic of your app, it might make more sense to use users.count here instead. Your call.
end
end
# Booking
class Booking < ApplicationRecord
belongs_to :user
belongs_to :training
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :training_id, presence: true
# It might make sense to only validate this on create. Get rid of the `#` on the below line if you think so.
validate :training_not_full?#, on: :create
private
def training_not_full?
errors.add(:training, "The training session is full!") unless training.can_book?
end
end
When you get to the if #booking.save in the controller, #booking.valid? is automatically called. If that returns false, then #booking.save will not save the record, and will also return false. This way, you can control persistence logic through your own validations in the models. The controller logic doesn't need to change at all.
I recommend reading about rails validations here. Skip to 6.2 for the relevant section.
Also, as a word of warning, default_scopes usually wind up doing more harm then good. Eventually you'll have a use case where you'll want to order by something else, and you'll find yourself working around the scope pretty often. You'll probably save yourself some headaches down the line if you get rid of it now.
I created a post_category table to add a category to specifics posts.
For example I created post_categories as Countries, Like Japan or China. And I want to create post which are come from culture or mode from countries like Japan or China. I focused only on post_categories as countries for now and below is the code I did.
I created this PostCategory, here are the migration and model
create_table "post_categories", force: :cascade do |t|
t.string "name"
t.string "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
class PostCategory < ActiveRecord::Base
has_many :posts, dependent: :destroy
NAMES = ["Japon", "Chine", "Corée du Sud", "Moyen Orient", "Indien"]
validates :name, inclusion: { in: PostCategory::NAMES, allow_nil: false }
end
And I created a Post with the PostCategory foreign key, here are the migration and model
create_table "posts", force: :cascade do |t|
t.string "cover"
t.string "subtitle"
t.string "title"
t.text "introduction"
t.text "body"
t.text "conclusion"
t.string "tag"
t.string "source"
t.string "link"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "post_category_id"
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :post_category, dependent: :destroy
TAGS = ["Design", "Mode", "Tendance", "Life-Style", "Tradition", "Gastronomie", "Insolite", "Technologie"]
validates :tag, inclusion: { in: Post::TAGS, allow_nil: false }
mount_uploader :cover, ImageUploader
end
I want to create a category with a simple form collection and I want i will be displayed on the post show#view
Here is the post_categories controller
class PostCategoriesController < ApplicationController
# before_action :set_post_category, only: [:show, :new, :create, :destroy]
def show
#post_category = PostCategory.find(params[:id])
end
def index
#post_categories = PostCategory.all
end
def create
#post_category = post_categories.new(post_category_params)
if #post_category.save
redirect_to #post
else
render 'post_categories/show'
end
end
def new
#post_category = PostCategory.new
end
def edit
end
def update
end
def destroy
#post_category = PostCategory.find(params[:id])
#post_category.destroy
redirect_to post_path(#post)
end
private
# def set_post
# #post = Post.find(params[:post_id])
# end
def find_post_category
#post_category = PostCategory.find(params[:id])
end
def post_category_params
params.require(:post_category).permit(:name, :description)
end
end
And here is the posts controller
class PostsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show]
before_action :find_post, only: [:show, :edit, :update, :destroy]
def index
#posts = Post.all
end
def show
# #alert_message = "Vous lisez #{#post.title}"
end
def new
# if current_user and current_user.admin?
#post = Post.new
# else
# redirect_to posts_path
# end
end
def create
# if current_user and current_user.admin?
#post = #post_category.posts.new(post_params)
##post = current_user.posts.new(post_params)
if #post.save
redirect_to #post
else
render :new
end
# else
# render 'shared/404.html.erb'
# end
end
def edit
end
def update
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
def destroy
#post.destroy
redirect_to :back
end
private
# def find_post
# #post = Post.find(params[:id])
# end
def set_post_category
#post_category = PostCategory.find(params[:post_category_id])
end
def post_params
params.require(:post).permit(:title, :subtitle, :introduction, :body, :cover, :tag, :post_category_id)
end
end
I don't know what views could I do create and how calling the post new#view because I configured my routes like that, and I need a post_category_id.
resources :post_categories do
resources :posts
end
That's I have to use this following path
post_category_posts GET /post_categories/:post_category_id/posts(.:format) posts#index
POST /post_categories/:post_category_id/posts(.:format) posts#create
new_post_category_post GET /post_categories/:post_category_id/posts/new(.:format) posts#new
edit_post_category_post GET /post_categories/:post_category_id/posts/:id/edit(.:format) posts#edit
post_category_post GET /post_categories/:post_category_id/posts/:id(.:format) posts#show
PATCH /post_categories/:post_category_id/posts/:id(.:format) posts#update
PUT /post_categories/:post_category_id/posts/:id(.:format) posts#update
DELETE /post_categories/:post_category_id/posts/:id(.:format) posts#destroy
post_categories GET /post_categories(.:format) post_categories#index
POST /post_categories(.:format) post_categories#create
new_post_category GET /post_categories/new(.:format) post_categories#new
edit_post_category GET /post_categories/:id/edit(.:format) post_categories#edit
post_category GET /post_categories/:id(.:format) post_categories#show
PATCH /post_categories/:id(.:format) post_categories#update
PUT /post_categories/:id(.:format) post_categories#update
DELETE /post_categories/:id(.:format) post_categories#destroy
I want to add the category on my show#view post and create a multisearch access to find posts added to a specific category. Thank you for your help
The way you have setup your relations since has a very big flaw - a post can only belong to a single category since the id is stored on the posts table.
Instead you would commonly use a many to many relationship:
class Post < ActiveRecord::Base
has_many :categories, :categories
has_many :post_categories
end
class Category < ActiveRecord::Base
has_many :posts
has_many :posts, through: :categories
end
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
validates_uniqueness_of :category_id, scope: :post_id
end
Here the categorizations table acts as a join table which links Post and Category - A post can have any number of categories and vice versa.
ADDED:
To create the join model and a migration to create table you would do:
rails g model Categorization post:belongs_to category:belongs_to
Using belongs to in the generator by default creates foreign keys for post_id and category_id.
You might want to add a compound uniqueness constraint in the migration as well.
def change
# ...
add_index(:categorizations, [:post_id, :category_id], unique: true)
end
Your on the right track with your CategoriesController but I would not use a nested route to create posts. Instead you might want to just use a plain old rails controller and let the user select categories via check boxes:
<%= form_for(#post) do |f| %>
<%= f.collection_check_boxes :category_ids, Category.all, :id, : name %>
<% end %>
You would then add the category_ids param to the whitelist:
def post_params
params.require(:post)
.permit(:title, :subtitle, :introduction,
:body, :cover, :tag, category_ids: []
)
end
The weird category_ids: [] syntax is because we want to allow an array of scalar values.
Querying for posts from a category by id can be done like so:
Post.joins(:categories).where(category: 1)
You can even select multiple categories by passing an array:
Post.joins(:categories).where(category: [1, 2, 3])
To hook this together from the view you would use a link (for a single category) or a form (yes forms can be used for GET) with checkboxes or selects.
Dealing with textual input is a bit trickier. A naive implementation would be:
Post.joins(:categories).where(category: { name: params[:search_query] })
However the user input would have to match exactly. There are several gems that provide search features to active record. However I would wait with the feature until you have a bit more experience as it can be tricky to implement.
See:
Rails guides: The has_many :through Association
Rails guides: Strong Parameters
Rails api: collection_check_boxes
I have an Order and an OrderTransactions model in my Rails4 application. They have a basic has_one and belongs_to relationship between them.
I'm posting requests from /orders/new page to the bank's URL as you can see below:
<%= simple_form_for(#order, :url => "https://testsanalpos.est.com.tr/servlet/est3Dgate", :method => :post) do |f| %>
<% #hashing.each do |k, v| %>
<%= f.input k, input_html: {name: k, value: v}, as: :hidden %>
<% end %>
<%= f.input :participation_id, ... %>
<%= f.button :submit, "Ödeme Yap" %>
<% end %>
The #hashing, hash is coming from my controller =>
class OrdersController < ApplicationController
before_filter :authenticate_user!
before_action :set_order, only: [:show, :edit, :update, :destroy]
skip_before_action :verify_authenticity_token
def new
#order = Order.new
#hashing = {
clientid: POS['clientid'],
oid: Time.now.to_i.to_s,
amount: POS['amount'],
okUrl: POS['okUrl'],
failUrl: POS['failUrl'],
rnd: Time.now.to_i.to_s,
}
end
def create
#order = Order.new(order_params)
respond_to do |format|
#order.purchase
end
end
def success
end
def fail
end
private
def set_order
#order = Order.find(params[:id])
end
def order_params
params.require(:order).permit(:id, :ip_address, :first_name, :last_name, :card_brand, :card_number, :card_verification, :card_expires_on, :user_id, :participation_id)
end
end
Order.rb =>
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :participation
has_one :transaction, :class_name => "OrderTransaction"
def purchase
participation.update_attribute(:payment_status, true)
create_transaction(:action => "purchase", :response => response)
end
end
The bank's page is getting all necessary information from the user like credit card number, card expiration date etc. My application is not doing anything about purchase process, all of them are happening on the bank's side.
Then the bank is returning me a bunch of parameters about payment process. If the payment is success full, bank is posting the parameters to my /orders/success.html.erb and if it fails it is posting to /order/fail.html.erb.
I have 2 problems =>
1) I want the Order model instance is created whatever the response is successful or failed. It seems like that should be happening by create method in controller but it not working :/
2) How can I get the parameters that the bank send to my /fail or /success URL? I need to get them into my application and save them as a OrderTransaction instance in my database. I can see the parameters in my logs like this =>
Started POST "/orders/fail" for 127.0.0.1 at 2014-06-01 13:40:28 +0300
Processing by OrdersController#fail as HTML
Parameters:
{
"TRANID"=>"",
"Faturafirma"=>"OMÜ Uzaktan Eğitim Merkezi",
"islemtipi"=>"Auth",
"refreshtime"=>"5",
"lang"=>"tr",
"amount"=>"30",
"ACQBIN"=>"490740",
"clientIp"=>"193.140.28.145",
"name"=>"AKBANK",
"cardHolderName"=>"dsadas dasdsa",
"okUrl"=>"http://localhost:3000/orders/success",
"storetype"=>"3d_pay_hosting",
"Response"=>"Declined"
....
}
DB Schema =>
create_table "orders", force: true do |t|
t.integer "participation_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "order_transactions", force: true do |t|
t.integer "order_id"
t.string "clientip"
t.string "cardholdername"
t.string "response"
t.string "errmsg"
...
t.datetime "created_at"
t.datetime "updated_at"
end
Routes.rb =>
...
post 'orders/success' => 'orders#success'
post 'orders/fail' => 'orders#fail'
resources :orders, only: [:index, :new, :create]
...
Suggest:
Move your creation success failure into a model and pass it such.
def success
Order.create_from_params(order_params)
end
def fail
Order.create_from_params(order_params)
end
Then handle success failure from params using response Decline etc..
class Order<ActivewRecord::Base
def self.create_from_params(order_params)
self.create(inflatewithfields) && self.purchase if params[response'] == 'success'
self.create(inflatewithfields) if params[response'] == 'Decline'
end
end
I have a blog that use devise for authentication and where user can create posts, I have created a basic dashboard where users will be able to manage their posts
this is my dashboard controller
before_action :authenticate_user!
def index
#posts = Post.find(:all, :conditions => [ "user_id = ?", #current_user ])
end
this my dashboard view
<% #posts.each do |post| %>
<%= post.content %>
<% end %>
this my user model
has_many :posts
and my post model
belongs_to :user
this is my schema.rb
create_table "posts", force: true do |t|
t.text "content"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
but when i go to the dashboard i am not getting any of my post and when i change my dashboard controller to
def index
#posts = Post.all
end
I'am getting all the posts so can someone tell me where is my fault
The find(:all, :conditions => []) was deprecated in Rails 3.2. Instead try:
def index
#posts = Post.where(:user_id => current_user)
end
Also, make sure you are adding a user_id in the create method in your controller:
def create
#post = post.new(post_params)
#post.user_id = current_user
REST OF CODE GOES HERE
end
Replace
#posts = Post.find(:all, :conditions => [ "user_id = ?", #current_user ])
with
#posts = #current_user.posts
I have a super simple question. I have a page that lists all the products in my app. I just want to make that page view-able by admin only. But products/new I want everyone to be able to see clearly.
schema.rb
create_table "users", :force => true do |t|
t.string "email"
t.string "password_hash"
t.string "password_salt"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "name"
t.boolean "admin", :default => false
end
products controller
class ProductsController < ApplicationController
before_filter :require_login
before_filter :current_user, only: [:create, :destory]
before_filter :correct_user, only: :destory
def index
#products = Product.all
end
def new
#product = Product.new
end
def create
#product = current_user.products.new(params[:product])
if #product.valid?
#product.save
render "show", :notice => "Sale created!"
else
render "new", :notice => "Somehting went wrong!"
end
end
Put in your controller
before_filter :authorize_admin, only: :index
and in application_controller.rb
def authorize_admin
redirect_to :back, status: 401 unless current_user.admin
#redirects to previous page
end
In your controller write
before_filter :admin_user
and create a def like this
private
def admin_user
redirect_to(root_path) unless current_user && current_user.admin?
end
What have you tried? It's not exactly a thought-provoking question, you have an boolean for admin, and you want to restrict an action to admin only, so just check current_user.admin.
before_filter :require_admin, only: :index
private
def require_admin
if !current_user.admin
if request.xhr?
head :unauthorized # for asynchronous/api requests, if you want.
else
render 'access/no_access' and return # or whatever.
end
end
end
in your ProductsController you can add a function that verify if the user is an admin or not and use filter for the view you want to protect like this :
class ProductsController < ApplicationController
before_filter :admin_user, only: :index # here you specify the action (for views) to protect
.
.
.
private
.
.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
i hope that help you
add correct_user method and admin_user method under private on your controller or create another method with following defination and add :only => :index on before_filter for admin.
before_filter :require_login
before_filter :correct_user
before_filter :admin_user, :only => :index
private
def correct_user
redirect_to(root_path) if current_user.nil? && !current_user.admin?
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
Check Railscasts Episode Super Simple Authentication.
Actually, Ryan Bates is a big fan of making authentication from scratch, he made many episode on this topics. Have a look on them and you will surely get some good ideas.