How can you limit the records that are available to the show action? The problem I'm having is that you can manually change the ID in the URL and look at projects that do not belong to the company.
My Routes Look like this:
/companies/:id/projects/:id
This is the show action
projects_controller.rb
def show
#project = Project.find(params[:id])
#company = Company.find(params[:company_id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #project }
end
end
routes.rb
resources :companies do
resources :projects
resources :employees
resources :requests do
put 'accept', :on => :member
end
end
project.rb
class Project < ActiveRecord::Base
attr_accessible :title
belongs_to :company
validates :title, presence: true
end
company.rb
class Company < ActiveRecord::Base
attr_accessible :name
has_many :projects
end
Assuming you have a has_many relationship between Company and Project I would change your controller code to this:
def show
#company = Company.find(params[:company_id])
#project = #company.projects.find(params[:id])
end
Keep in mind though that this does not really solve your problem as people can still change the company_id and view other companies easily. What you need is a more solid authorization framework like CanCan that prevents unauthorized access to resources.
May be, you should use smth like this:
#project = #company.projects.find(params[:id])
Check this for details.
Try to change the action to this
def show
#company = Company.find(params[:company_id])
#project = #company.projects.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #project }
end
end
Related
What I have at the moment is pretty standard set of code, where all child objects can be list under only their parent objects.
customer.rb
class Customer < ApplicationRecord
has_many :bookings, dependent: :delete_all
end
booking.rb
class Booking < ApplicationRecord
belongs_to :customer
has_many_attached :images
end
routes.rb
Rails.application.routes.draw do
resources :customers do
resources :bookings
end
end
bookings_controller.rb
This has been automatically generated. I only removed comments and json
related lines.
class BookingsController < ApplicationController
before_action :set_customer
before_action :set_booking, only: %i[show edit update destroy]
def index
#bookings = Booking.all.with_attached_images
end
def show; end
def new
#booking = #customer.bookings.build
end
def edit; end
def create
#booking = #customer.bookings.build(booking_params)
respond_to do |format|
if #booking.save
format.html { redirect_to #customer, notice: 'Booking was successfully created.' }
else
format.html { render :new }
end
end
end
def update
respond_to do |format|
if #booking.update(booking_params)
format.html { redirect_to [#customer, #booking], notice: 'Booking was successfully updated.' }
else
format.html { render :edit }
end
end
end
def destroy
#booking.destroy
respond_to do |format|
format.html { redirect_to customer_bookings_url, notice: 'Booking was successfully destroyed.' }
end
end
private
def set_customer
#customer = Customer.find(params[:customer_id])
end
def set_booking
#booking = #customer.bookings.find(params[:id])
end
def booking_params
params.require(:booking).permit(:name, :category, :rooms, :wifi, :phone, :address, :description, :available, :check_in, :check_out, :customer_id, images: [])
end
end
I want to list all child objects for all parent objects.
I guess, I will have to modify routes as follows
routes.rb
Rails.application.routes.draw do
root 'customers#index'
resources :customers do
resources :bookings
end
resources :bookings
end
I will also need to modify bookings_controller.rb
By commenting out the line before_action :set_customer, otherwise I will get an error like Couldn't find Customer without an ID
And I will have to put #customer = Customer.find(params[:customer_id]) for all methods except index. Which means I won't be following DRY concept...
Any other better approach to solve this?
Your approach is the best already in my opinion, just need to utilize Rails helpers correctly to keep your code DRY.
By commenting out the line before_action :set_customer, otherwise I
will get an error like Couldn't find Customer without an ID
And I will have to put #customer = Customer.find(params[:customer_id])
for all methods except index. Which means I won't be following DRY
concept...
NO you don't have to.
If the index action of customers/bookings_controller is not used anywhere else then just remove that action from the controller file and specify the same in the route file as:
resources :customers do
resources :bookings, except: :index
end
If the index action is still being used in other places then Rails callbacks can be declared with except option as below to specify that the set_customer will be called for all actions except the index.
before_action :set_customer, except: :index
More about Rails Controller Callback options here
Other points that you may want to check:
dependent: :delete_all. With this, there will be orphan active_storage_attachments records in your db when you delete a customer. Because it triggers the callback that deletes only the associated bookings when leave the attached images of those bookings untouched. Reference
resources :bookings (last line of your route file). If you only have the index action in the controller, you should declare the same here also as resources :bookings, only: :index
I'm working on an app which has many 'Activities'. Each 'Activity' has many 'Ranks'. I'd like each 'Activity' to have a page called grading, where the user can see a list of all of that activity's ranks and conveniently update them. I imagine the URL would be something like http://localhost:3000/activities/21/grading
I'm already using http://localhost:3000/activities/21/edit for its intended purpose.
I don't need a model for gradings, as I don't need to save any grading records.
I know exactly what to put in the view, I'm just unsure what to add to the controller and routes files. Other people have worked on this app but I'm unable to contact them.
Routes
resources :activities do
collection do
get 'scheduled_classes'
end
end
resources :ranks
end
activities_controller
class ActivitiesController < ApplicationController
def new
#activity = Activity.new
#activity.timeslots.build
#activity.ranks.build
end
def create
#activity = current_club.activities.new(activity_params)
if #activity.save
flash[:success] = "New class created!"
redirect_to activity_path(#activity)
else
render 'new'
end
end
def edit
#activity = current_club.activities.find_by(id: params[:id])
#active_ranks = #activity.ranks.where(active: true)
if !#activity.active?
redirect_to activities_path
else
#activity.timeslots.build
end
end
def update
#activity = current_club.activities.find_by(id: params[:id])
if #activity.update_attributes(activity_params)
flash[:success] = "Class updated!"
redirect_to edit_activity_path(#activity)
else
render 'edit'
end
end
def show
#activity = current_club.activities.find_by(id: params[:id])
#active_ranks = #activity.ranks.where(active: true)
if #activity.nil?
redirect_to root_url
elsif !#activity.active?
redirect_to activities_path
end
end
def index
#activities = current_club.activities.all
end
def destroy
#activity = current_club.activities.find_by(id: params[:id])
if #activity.nil?
redirect_to root_url
else
#activity.destroy
flash[:success] = "Class deleted"
redirect_to activities_path
end
end
end
private
def activity_params
params.require(:activity).permit(:name, :active,
:timeslots_attributes => [:id,
:time_start,
:time_end,
:day,
:active,
:schedule],
:ranks_attributes => [:id,
:name,
:position,
:active])
end
end
activity
class Activity < ApplicationRecord
belongs_to :club
has_many :timeslots, dependent: :destroy
accepts_nested_attributes_for :timeslots,:allow_destroy => true
has_many :ranks, dependent: :destroy
has_many :attendances, dependent: :destroy
accepts_nested_attributes_for :ranks
validates :club_id, presence: true
validates :name, presence: true, length: { maximum: 50 }
end
Your routes don't need to have an associated model or resource.
resources :activities do
collection do
get 'scheduled_classes'
end
member do
get :grading
end
end
will match to activities#grading
See https://guides.rubyonrails.org/routing.html#adding-member-routes for more info.
As you want to add a route on a particular activity, you should add member route on the activity like below,
resources :activities do
collection do
get 'scheduled_classes'
end
get :grading, on: :member
end
Apart from this, you have to add method in ActivitiesController for this route like below,
def grading
#activity = Activity.find_by(id: params[:id])
# do more here
end
In view files, you can create grading.html.erb under activities resources and put your view code there.
I'm trying to make that a subscriber, sub to an certain event
with the following url per example:
http://localhost:3001/events/1/subscribers/new
but I don't know how to associate event_id when creating a new subscriber
for the moment i'm getting this error:
Couldn't find Event with 'id'=
in the routes:
resources :events do
resources :subscribers #url/events/:events_id/subscribers/new
end
resources :events
root 'events#index'
in the subscribers controller:
def show
end
# GET /subscribers/new
def new
#puts "Look for me in console\n"
#puts params.inspect
#event = Event.find(params[:events_id])
#subscriber = #event.Subscriber.new
end
# GET /subscribers/1/edit
def edit
end
# POST /subscribers
# POST /subscribers.json
def create
#event = Event.find(params[:order_id])
#subscriber = #event.Subscriber.new order_params
##subscriber = Subscriber.new(subscriber_params)
respond_to do |format|
if #subscriber.save
SubsMailer.new_subscriber(#subscriber).deliver
format.html { redirect_to #subscriber, notice: 'Subscriber was successfully created.' }
format.json { render :show, status: :created, location: #subscriber }
else
format.html { render :new }
format.json { render json: #subscriber.errors, status: :unprocessable_entity }
end
end
end
in the new.html.erb:
<h1>New Subscriber</h1>
<%= render 'form', subscriber: #subscriber %>
<%= link_to 'Back', subscribers_path %>
model association:
event.rb:
class Event < ApplicationRecord
has_many :subscribers, dependent: :destroy
end
subscriber.rb:
class Subscriber < ApplicationRecord
belongs_to :event
validates :email, presence: true,
format: /\A\S+#\S+\z/,
uniqueness: { case_sensitive: false }
end
Well, I think this documentation will help you to understand what you need to do.
If briefly at first you need to change your models. You could have many to many for Event -> Subscriber association or one to many. One to many is the simplest to show so you need to add this to your Subscriber model:
belongs_to :event
And this to your Event model:
has_many :subscribers
Add new migration:
def change
remove_column :subscribers, :events_id
remove_column :subscribers, 'Event_id'
add_column :subscribers, :event_id, :integer
end
Then in your controller, you should change method calls, as Subscriber is a class, not the method.
def new
#event = Event.find(params[:event_id])
#subscriber = #event.subscribers.build
end
And you should be sure that in your database you have Event with this id.
To check it you can try to debug your controller code:
def new
puts "Event ids: " + Event.all.map(&:id).inspect
#event = Event.find(params[:event_id])
#subscriber = #event.subscribers.build
end
In your logs you should have something like:
Event ids: [1]
I think you just have a typo in your new method. You call params[:eventS_id] when it should be params[:event_id]. Also you don't properly reference your association. it should be event.subscribers.new:
def new
#puts "Look for me in console\n"
#puts params.inspect
#event = Event.find(params[:event_id])
#subscriber = #event.subscribers.build
end
Migration:
def up
change_table :subscribers do |t|
t.remove :Event_id
t.references :event
end
end
def down
change_table :subscribers do |t|
t.remove :event_id
t.add :Event_id, :integer
end
end
Keep me posted whether this helps and if you have any additional issues
I can't get a like system working for my Rails discussion forum:
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :comment
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
has_many :likes, through: :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
has_many :likes
end
routes.rb:
resources :posts do
resources :comments do
resources :likes do
put "/create", to: "likes#create"
end
end
end
likes_controller.rb:
class LikesController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = Comment.find(params[:comment_id])
#like = Like.find(params[:like_id])
#user = current_user.id
Like.create(like_id: #like, post_id: #post, comment_id: #comment, user_id: #user)
respond_to do |format|
format.html { redirect_to post_path(#post) }
end
end
end
rake routes:
post_comment_like_create PUT /posts/:post_id/comments/:comment_id/likes/:like_id/create(.:format) likes#create
post_comment_likes GET /posts/:post_id/comments/:comment_id/likes(.:format) likes#index
POST /posts/:post_id/comments/:comment_id/likes(.:format) likes#create
etc
The problem I keep running into is that it's missing like_id:
No route matches {:action=>"create", :comment_id=>"218", :controller=>"likes", :like_id=>nil, :post_id=>"30"} missing required keys: [:like_id]
What am I doing wrong? I assume most of the code is correct, as the only error it's giving me is a missing ID, I just don't understand how or where to fetch that like_id.
EDIT:
This is the action I'm trying to use:
= link_to post_comment_like_create_path(#post, comment, #like)
The issue is partly in your routes, partly in your controller and partly in the view.
By using the line resources :likes, you are creating routes for the 7 CRUD actions automatically. This means you do not need to manually declare a create action in your routes (which should be a POST rather than PUT). I'd suggest reading the Rails Guide on Routing.
In your controller, you are attempting to create an object called #like by finding a Like based on an ID. But you are creating this for the first time, so there is nothing to find.
In your view, you shouldn't be using a link_to for anything that affects the database but rather a button_to and the path you are using is also part of the problem.
Make the following changes:
routes.rb
resources :posts do
resources :comments do
resources :likes
end
end
likes_controller.rb
class LikesController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = Comment.find(params[:comment_id])
#user = current_user.id
Like.create(post_id: #post, comment_id: #comment, user_id: #user)
respond_to do |format|
format.html { redirect_to post_path(#post) }
end
end
end
view
= button_to post_comment_likes_path(#post, comment)
I would suggest you look at the guides around nesting of routes. Nesting this deep can easily become cumbersome.
I'm trying to get my head around the best way to add a record to a join table through alternative controllers in rails.
I have various models in my app that will require this, but I'm focusing on these two first before I transcribe the method into others, so shall use this as the example. I have a Venue and Interest model which are to be connected through VenuesInterests model (it has a couple of extra optional attributes so isn't a HABTM relationship). A user can admin a Venue instance and/or an Interest instance and therefore there should be an ability to select Venues to attach to an Interest and likewise Interests to attach to a Venue. This should be done with an Add Venues link on the Interest instance view and an Add Interests link on the Venue instance view. This would then take the user to a list of the relevant instances for them to select ones they would like to select.
Here are my models:
Venue.rb
class Venue < ActiveRecord::Base
has_many :interests, through: :venue_interests
has_many :venues_interests, dependent: :destroy
accepts_nested_attributes_for :venues_interests, :allow_destroy => true
end
Interest.rb
class Interest < ActiveRecord::Base
has_many :venues, through: :venue_interests
has_many :venues_interests, dependent: :destroy
end
VenuesInterests.rb
class VenuesInterest < ActiveRecord::Base
belongs_to :interest
belongs_to :venue
validates :interest_id, presence: true
validates :venue_id, presence: true
end
This all seems fine, however it's the controller and views that I'm struggling with. I've tried adding an extra method add_interest to the Venues controller to do the job of the create method in the VenuesInterests controller, so that there will be a different view when adding Venues to an Interest than there would be adding Interests to a Venue, otherwise I don't know how I would do this. My current Venues controller is as follows:
VenuesController.rb:
class VenuesController < ApplicationController
before_filter :authenticate_knocker!, only: [:new, :edit, :create, :update, :destroy]
respond_to :html, :json
def index
#venues = Venue.all.paginate(page: params[:page]).order('created_at DESC')
end
def show
#venue = Venue.find(params[:id])
#hash = Gmaps4rails.build_markers(#venue) do |venue, marker|
marker.lat venue.latitude
marker.lng venue.longitude
marker.infowindow venue.name
end
end
def new
#venue = Venue.new
end
def edit
#venue = Venue.find(params[:id])
end
def create
#venue = current_knocker.venues.create(venue_params)
respond_to do |format|
if #venue.save!
format.html { redirect_to #venue, notice: 'Venue was successfully created.' }
format.json { render json: #venue, status: :created, location: #venue }
else
format.html { render action: "new" }
format.json { render json: #venue.errors, status: :unprocessable_entity }
end
end
end
def update
#venue = Venue.find(params[:id])
#venue.update_attributes(venue_params)
respond_to do |format|
if #venue.update_attributes(venue_params)
format.html { redirect_to(#venue, :notice => 'Your Venue was successfully updated.') }
format.json { respond_with_bip(#venue) }
else
format.html { render :action => "edit" }
format.json { respond_with_bip(#venue) }
end
end
end
def destroy
end
def add_interests
#venues_interests = VenuesInterest.new
#interests = Interests.all.paginate(page: params[:page]).order(:name)
end
private
def venue_params
params.require(:venue).permit(:admin... etc)
end
end
This isn't currently working as I'm not sure how to reference other classes within a controller, but the important thing I'd like to know is is there a better way to do this or am I (kind of) on the right track? If anyone has a good method (perhaps a jQuery plugin) for allowing multiple selection of instances for the view, that would be great too!
In my opinion, I would take advantage of the existing update method to add the relationship between Interest and Venue. I can do like this:
def update
#venue = Venue.find(params[:id])
#venue.update_attributes(params[:venue_params])
if params[:interest_ids].present?
#venue.interests = Interest.where(id: params[:interest_ids])
#venue.save
end
#more code to handle the rendering
end