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.
Related
I am practicing rails by developing a band application where Venues have many events and bands and those events and bands belong to the Venues.
I have foreign keys set up, I am just a little confused on how to display attributes of the venues controller within the Events index.html.erb
I want to show which specific Venue my event belongs to and vice versa.
This is my events controller
class EventsController < ApplicationController
def index
#events = Event.all
#should I store my venues in the index to show its values?
#venues = Venue.where(params[:id])
end
def edit
#events = Event.find(params[:id])
puts #events.inspect
end
def update
#events = Event.find(params[:id])
#events.update_attributes(name: params[:event][:name], date: params[:event][:date], alcohol_served: params[:event][:alcohol_served], venue_id: params[:event][:venue_id], band_id: params[:event][:band_id])
redirect_to action: 'index'
end
def destroy
#events=Event.find(params[:id])
#events.destroy
redirect_to action: 'index'
end
def new
#Events = Event.new
end
def create
#Events = Event.create(name: params[:event][:name], date: params[:event][:date], alcohol_served: params[:event][:alcohol_served], venue_id: params[:event][:venue_id], band_id: params[:event][:band_id])
#Events.save
redirect_to action: 'index'
end
end
Disregard some of my redirects they are only for test purposes
Here are my models
class Venue < ApplicationRecord
has_many :events
has_many :bands, through: :events
end
class Event < ApplicationRecord
belongs_to :venue
belongs_to :band
end
class Band < ApplicationRecord
has_many :events
end
If you have a Venue and want to know all the events taking place at this Venue: #events = Event.find_by(:venue_id venue.id)
If you have an event and want to find the venue
venue_id = Event.select(:venue_id).find(params[:id])
#venue = Venue.find(venue_id)
You can replace in params[:id] by event.id depending on the use case.
In my app I have Permission table, which stores all the logic what User can do. With Pundit I want to allow User to create new Campaign if Permission table allows. User can access Campaigns and create new, if Permission table contains this info:
permitable_type: Sysmodule // another table where I store info on System sections, where Campaigns is one of
permitable_id: 2 // means Campaigns from Sysmodule
level: 3 // means User can edit something in Campaigns section
So far I keep getting error "Pundit::NotDefinedError", unable to find policy of nil policies/application_policy.rb is standart, no changes.
Obviously I am doing sothing wrong. How do I do this authorization correctly? Many thanks for any help! I am on Rails 5 + Pundit.
models/permission.rb
class Permission < ApplicationRecord
belongs_to :permitable, polymorphic: true
belongs_to :user
enum level: {owner: 1, view: 2, edit: 3}
end
models/user.rb
has_many :permissions
has_many :campaigns, through: :permissions, source: :permitable, source_type: 'Campaign' do
def owner_of
where('`permissions`.`level` & ? > 0', Permission::owner )
end
end
has_many :sysmodules, through: :permissions, source: :permitable, source_type: 'Sysmodule' do
def can_access
where('`permissions`.`level` & ? > 1', Permission::can_access )
end
end
controllers/campaigns_controller.rb
def new
#campaign = Campaign.new
authorize #campaign
end
policies/campaign_policy.rb
class CampaignPolicy < ApplicationPolicy
attr_reader :user, :campaign, :permission
#user = user
#permission = permission
end
def new?
user.permission? ({level: 3, permitable_type: "Sysmodule", permitable_id: 2})
end
views/campaigns/index.html.erb
<% if policy(#campaign).new? %>
</li>
<li><%= link_to "New campaign", new_campaign_path(#campaign) %></li>
</li>
<% end %>
Instead of dealing directly with what permissions a user should have try thinking of it what roles users can have in a system.
This makes it much easier to create authorization rules that map to real world problems.
Lets imagine an example where we have users and groups. The rules are as follows:
groups can be created by any user
the user that creates the group automatically becomes an admin
groups are private
only admins or members can view a group
only an admin can modify a group
The models:
class User < ApplicationRecord
rolify
end
class Group < ApplicationRecord
resourcify
end
The policy:
class GroupsPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.with_roles([:admin, :member], current_user)
end
end
def show?
user.has_role?([:member, :admin], record)
end
def index?
true
end
def create?
true # any user can create
end
def new?
create?
end
def update?
user.has_role?(:admin, record)
end
def edit?
update?
end
def destroy?
update?
end
end
The controller
class GroupsController < ApplicationController
respond_to :html
before_action :autenticate!
before_action :set_group!, only: [:show, :edit, :update, :destroy]
def show
respond_with(#group)
end
def index
#groups = policy_scope(Group.all)
respond_with(#groups)
end
def new
#group = authorize( Group.new )
end
def create
#group = authorize( Group.new(group_attributes) )
if #group.save
current_user.add_role(:member, #group)
current_user.add_role(:admin, #group)
end
respond_with(#group)
end
def edit
end
def update
#group.update(group_params)
respond_with(#group)
end
def destroy
#group.destroy
respond_with(#group)
end
private
def set_group!
#group = authorize( Group.find(params[:id]) )
end
def group_params
params.require(:group).permit(:name)
end
end
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.
In my Rails app, I have the following objects:
Group: has_many users through group_membership
GroupMembership: belongs_to user, belongs_to group
User: has_many groups through group_membership
Users can create groups. When this happens, I want to automatically add the user to the group. In my GroupsController, I have the following (extending InheritedResources):
super do |success, failure|
if success
GroupMembership.create(:user_id => current_user, :group_id => ???)
...
end
The problem is I cannot retrieve the object that super created. Is there a way to do this? Or better, is there a way to change the LockGroup model so that it always performs this association?
When the callback is fired, the controller already has the standard instance variable corresponding to the created group: #group !!
class GroupController < InheritedResources::Base
def create
super do |success, failure|
if success
GroupMembership.create(:user_id => current_user, :group_id => #group.id)
...
end
end
end
I assume the params key given for your group is :group. Then you can used the nested_attributes_for option in the model. Then you can set those in a before filter from the create action:
class User < ActiveRecord::Base
accept_nested_attributes_for :group_membership
end
# on your controller
before_filter :add_user, :on => [:create]
def add_user
params[:group][:group_membership_attributes] = {}
params[:group][:group_membership_attributes][:user] = current_user
end
or you build the group membership on user initialize:
class User < ActiveRecord::Base
def after_initialize
build_group_membership
end
end
# on your controller
before_filter :add_user, :on => [:create]
def add_user
params[:group][:user] = current_user
end
and it should automagically work.
If I have a nested resource like so:
resources :users
resources :posts
end
and a user has_many posts, it is possible to have Rails start numbering based on the parent association in the URL? For example, currently, nesting resources just grabs the ID:
#user.posts.find(params[:id])
This correctly namespaces the posts, only allowing posts from #user... however, is there a way such that the post_id is independent? I.E. I want each user's posts to start at 1, where:
/users/1/posts/1
/users/2/posts/1
Actually refer to two different posts?
It can be quite a bit of work, but basically you can do it with these steps:
Create a migration to add a new attribute to store the specific user-post count. (I used user_post_id)
Override Post's to_param method to use the new value you just created. (It has to be a string.)
to_param is the method that the url and path helpers use.
Create a before_save filter that will actually increment the user_post_id value for each new post.
Change all your controller methods to find on user_post_id
#user = User.find(params[:user_id])
#post = #user.posts.where(:user_post_id => (params[:id])).first
Change all your Views that might not work now
You can see the source here: Custom Nested Resource URL example
Code
migration:
class AddUserPostIdToPosts < ActiveRecord::Migration
def change
add_column :posts, :user_post_id, :integer
end
end
post.rb:
class Post < ActiveRecord::Base
before_save :set_next_user_post_id
belongs_to :user
validates :user_post_id, :uniqueness => {:scope => :user_id}
def to_param
self.user_post_id.to_s
end
private
def set_next_user_post_id
self.user_post_id ||= get_new_user_post_id
end
def get_new_user_post_id
user = self.user
max = user.posts.maximum('user_post_id') || 0
max + 1
end
end
A couple controller methods
posts_controller.rb:
class PostsController < ApplicationController
respond_to :html, :xml
before_filter :find_user
def index
#posts = #user.posts.all
respond_with #posts
end
def show
#post = #user.posts.where(:user_post_id => (params[:id])).first
respond_with [#user, #post]
end
...
end