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.
Related
I have a form for a User model that accepts_nested_attributes on a has_many association to Comment.
Whenever the form is updated, I need to be able to check whether a Comment was added/created along with it.
Aside from checking what's inside the submitted params, is there a more Rails way of doing this?
User model
class User < ActiveRecord::Base
accepts_nested_attributes_for :comments
...
end
View
= simple_form_for #user do |f|
= f.simple_fields_for :comments do |fc|
= fc.input :content
...
end
end
And finally in my controller
def update
#user = User.find(params[:id])
if #user.update_attributes(permitted_params)
# Check if a new comment was added here
end
end
def update
#user = User.find(params[:id])
if #user.update_attributes(permitted_params)
if #user.comments.any?(&:id_changed?)
# do_something
end
end
end
This code exploits ActiveModel::Dirty methods
Trying to figure out a better way of assigning a review it's associated models.
I have the following classes:
class User < ActiveRecord::Base
has_many :reviews, dependent: :destroy
end
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :restaurant
end
class Restaurant < ActiveRecord::Base
has_many :reviews, dependent: :destroy
end
Pretty straightforward stuff. A review must have a restaurant and a user. My create action looks like this:
def create
#restaurant = Restaurant.find(params[:restaurant_id])
#review = #restaurant.reviews.build(review_params)
#review.user = current_user
if #review.save
redirect_to #restaurant
else
render 'new'
end
end
private
def review_params
params.require(:review).permit(:content)
end
Currently I build the review for the restaurant and then I assign the review's user to the current user.
This all works fine but is there a cleaner way to build the associations?
Is there a way to add additional arguments to the build method alongside the strong params?
I looked at accepts_nested_attributes_for but I couldn't get it to work.
Thanks!
You can use merge in the review_params like below
def review_params
params.require(:review).permit(:content).merge(user_id: current_user.id)
end
so that you can erase this line #review.user = current_user in the create method
In your form, you can put a hidden field with the user_id that you want to assign:
<%= f.hidden_field :user_id, value: #user.id %>
Then, add it to your review_params:
params.require(:review).permit(:content, :user_id)
I have been trying to build an app offering discounted vacation trips such that:
(1) a user(travel agent) can compose a trip by combining hotels (hotel chains) and cities
(2) a user(regular user) can review hotels and cities, s/he has already visited.
(3) another user can evaluate how good the deal is with respect to the country and hotel the travel agent will have him/her stay.
The models look like this
class User < ActiveRecord::Base
has_many :trips
has_many :reviews
end
class Trip < ActiveRecord::Base
belongs_to :user
belongs_to :hotel
belongs_to :city
end
class Hotel < ActiveRecord::Base
belongs_to :city
has_many :reviews, as: :reviewable
end
class City < ActiveRecord::Base
has_many :hotels
has_many :reviews, as: :reviewabel
end
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :reviewable, polymorphic: true
end
The problem is I can figure out how to create the controllers for Hotel and City because they are only created in the context of a makeshift trip. I checked the rails casts on nested forms and the use of accepts_nested_attributes_for but I can't seem to get it right.
Note: the reason why I separated the hotels and the cities is to be able to retrieve the reviews independently. Such that Say I enjoyed my stay at the Four Seasons in Toronto but not the one in NY. - because of the cities/hotels (=> accommodating the case where I didn’t enjoy it because the hotel was crap and the one where I didn’t because the city was)
Note 2: I understand it doesn’t make much sense to seperate hotels and cities in this example - I made a mistake in self-appointing the tutorial. But the problem has been haunting me, what if it was an delivery order instead with entree/meal/dinner instead of hotels and cities, or restaurant chains and neighborhoods.
Any help is appreciated. Thank you
Edit
Edited after Settheline’s comment.
I mean the create actions for cities and hotels only exist in the context of a Trip create action.
Trip has 2 attributes: title & description: It’s only then that I “log” the itinerary. Here’s what my controllers look like to give you a better idea
class TripsController < ApplicationController
before_action :signed_in_user
def show
#trip = Trip.find(params[:id])
end
def index
#trips = current_user.Trip.all
end
def new
#trip = Trip.new
end
def create
# #trip = Trip.new(trip_params)
#trip = current_user.trips.build(trip_params)
if #trip.save
flash[:success] = "Your trip was successfully published!"
redirect_to #trip
else
render 'new'
end
end
def edit
end
def update
if #trip.update_attributes(trip_params)
flash[:success] = "Trip was updated"
redirect_to #trip
else
render 'edit'
end
end
def destroy
Trip.find(params[:id]).destroy
flash[:success] = "trip was deleted. Thank you"
redirect_to #user #root_url
end
private
def trip_params
params.require(:trip).permit(:title, :description)
end
end
class CitiesController < ApplicationController
before_action :signed_in_user
def create
#city = City.new(city_params)
if #city.save
# flash[:success] = ""
else
render 'new'
end
end
# def destroy
# City.find(params[:id]).destroy
# flash[:success] = “City was deleted."
# redirect_to root_url
# end
private
def city_params
params.require(:city).permit(:name, :province, :country)
end
end
class HotelsController < ApplicationController
before_action :signed_in_user
def create
#similar to city
end
def destroy
#similar to city
end
private
def hotel_params
params.require(:hotel).permit(:name, :address,
:management_contact,
:city_id)
end
end
And here’s the problem:
I want to have/add create forms within the trip one in
sample_app/app/views/trips/new.html.erb
<% provide(:title, 'New Trip') %>
<h1>New Trip</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#trip) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_field :title, placeholder: "Type in a title" %>
<%= f.text_field :description, placeholder: "Any additional info." %>
<%= f.submit "Publish", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
accepts_nested_attributes_for allows you to save attributes on associations. Although you do have associations in your models it doesn't necessarily mean that you need to use accepts_nested_attributes_for. It depends on how the code flows through your controllers.
Simple Example
For example, you would probably want to allow your users to view their trips and reviews. First you'll need a method to get the current user:
users_controller.rb
class ApplicationController < ActionController::Base
def current_user
#current_user ||= User.find(session[:user_id])
end
end
This method will be inherited by all of your controllers and allow them to get the current user. (There are many solutions out there for getting the current user, this is definitely not a good solution but it is OK for demonstration purposes).
Trips & Reviews
Now you can create some controllers for the current user to view their trips and reviews:
trips_controller.rb
class TripsController < ApplicationController
def index
#trips = current_user.trips.all
end
end
reviews_controller.rb
class ReviewsController < ApplicationController
def index
#reviews = current_user.reviews.all
end
end
Now you have some controller actions displaying the trips/reviews for the current user. I think this example demonstrates how you can create your controllers and that accepts_nested_attributes_for is not necessarily required.
I'm new to Rails. I'm building my first app - simple blog. I have User and Post models, where each user can write many posts. Now I want to add Comment model, where each post can have many comments, and also each user can comment on any post created by any other user.
In Comment model I have
id \ body \ user_id \ post_id
columns.
Model associations:
user.rb
has_many :posts, dependent: :destroy
has_many :comments
post.rb
has_many :comments, dependent: :destroy
belongs_to :user
comment.rb
belongs_to :user
belongs_to :post
So how do I correctly define create action in CommentsController?
Thank you.
UPDATE:
routes.rb
resources :posts do
resources :comments
end
comments_controller.rb
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(comment_params)
if #comment.save
redirect_to #post
else
flash.now[:danger] = "error"
end
end
The result is
--- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: rDjSn1FW3lSBlx9o/pf4yoxlg3s74SziayHdi3WAwMs=
comment: !ruby/hash:ActionController::Parameters
body: test
action: create
controller: comments
post_id: '57'
As we can see it doesnt send user_id and works only if I delete validates :user_id, presence: true string from comment.rb
Any suggestions?
In your way you should put this:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(comment_params)
#comment.user_id = current_user.id #or whatever is you session name
if #comment.save
redirect_to #post
else
flash.now[:danger] = "error"
end
end
And also you should remove user_id from comment_params as strong parameters .
Hope this will help you .
Associations
To give you a definition of what's happening here, you have to remember whenever you create a record, you are basically populating a database. Your associations are defined with foreign_keys
When you ask how to "add comments to User and Post model" - the bottom line is you don't; you add a comment to the Comment model, and can associate it with a User and Post:
#app/models/comment.rb
Class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
This prompts Rails to look for user_id and post_id in the Comment model by default.
This means if you wanted to create a comment directly, you can associate it to either of these associations by simply populating the foreign_keys as you wish (or use Rails objects to populate them)
So when you want to save a comment, you can do this:
#app/controllers/comments_controller.rb
Class CommentsController < ApplicationController
def create
#comment = Comment.new(comment_params)
end
private
def comment_params
params.require(:comment).permit(:user_id, :post_id, :etc)
end
end
Conversely, you can handle it by using standard Rails objects (as the accepted answer has specified)
Class CommentsController < ApplicationController
before_action :set_user
before_action :set_post
def create
#comment = #post.comments.create(comment_params)
if #comment.save
redirect_to #post
else
flash.now[:danger] = "error"
end
end
private
set_post
#post = User.posts.find(params[:post_id])
end
set_user
#user = User.find(params[:user_id])
end
comment_params
params[:comment].permit()
end
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.