I have what I thought is a simple event signup application. A user registers for the site and then can select an event, apply to participate in that event by updating some fields that are on the user model (At this point, just a first_name). A User can attend many Events, but must register (Participation) for each one. An Event can have many Users through Participations. Any help is greatly appreciated!
There are currently three models:
# user.rb
class User < ActiveRecord::Base
has_many :participations
has_many :events, through: :participations
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
# event.rb
class Event < ActiveRecord::Base
has_many :participations
has_many :users, through: :participations
end
# And a join table: participation.rb
class Participation < ActiveRecord::Base
belongs_to :user
belongs_to :event
accepts_nested_attributes_for :user
end
Here's my routes file:
routes.rb
Rails.application.routes.draw do
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
devise_for :users
root 'events#index'
resources :events do
resources :participations
end
resources :users
end
And I think the only applicable controller:
participations_controller.rb
class ParticipationsController < ApplicationController
def index
end
def new
#participation = Participation.new
#user = current_user
#event = Event.find(params[:event_id])
end
def create
#participation = Participation.new(participation_params)
if #participation.save
redirect_to events_path, notice: "You are registered!"
else
render action: 'new'
end
end
private
def participation_params
params.require(:participation).permit(:status, :user_attributes => [:id, :first_name])
end
end
The form should simply create a new participation based on the event_id, set its status, and update the user_attributes.
views/participations/new.html.erb
<%= form_for #participation, url: {action: "create"} do |f| %>
<%= f.label :status %><br />
<%= f.text_field :status %>
<%= f.fields_for :user, current_user do |builder| %>
<fieldset>
<%= builder.label :first_name, "First Name" %><br />
<%= builder.text_field :first_name %><br />
</fieldset>
<% end %>
<%= f.submit "Register" %>
<% end %>
Unfortunately, completing the form returns a 404 error with a missing participation_id.
Started POST "/events/1/participations" for ::1 at 2015-11-24 21:26:35 -0600
Processing by ParticipationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"0vVTyeGwGUheZPbOoMeyUvr2ciJG2OpXwqToc2pYLr2HXDMhogX8llESiG8Z4Cc5Pq5sBmiHi43rvjHka7K3yA==", "participation"=>{"status"=>"done", "user_attributes"=>{"first_name"=>"sone", "id"=>"1"}}, "commit"=>"Register", "event_id"=>"1"}
Completed 404 Not Found in 3ms (ActiveRecord: 0.0ms)
ActiveRecord::RecordNotFound - Couldn't find User with ID=1 for Participation with ID=:
Well as described here, what is happening is that when you pass an id to the nested model, accepts_nested_attributes will look and try to update the model you are looking for.
So at that moment there is not such association between current user and the Participation you want to create, that's why you get the error:
Couldn't find User with ID=1 for Participation with ID=:
That means there is not such user with ID=1 associated with your participation
My suggestion:
Instead of add nested attributes for user, why not just add the fields you need to the Participation model?
Add the first_name attribute to your participation model and in your controller do the following:
class ParticipationsController < ApplicationController
def index
end
def new
#participation = Participation.new
#participation.user = current_user
#participation.event = Event.find(params[:event_id])
end
def create
#participation = Participation.new(participation_params)
#participation.user = current_user
#participation.event = Event.find(params[:event_id])
if #participation.save
redirect_to events_path, notice: "You are registered!"
else
render action: 'new'
end
end
private
def participation_params
params.require(:participation).permit(:status, :first_name)
end
end
Then in your form you can just make a normal first_name input:
<fieldset>
<%= f.label :first_name, "First Name" %><br />
<%= f.text_field :first_name %><br />
</fieldset>
Try that and let me know, by the way do not forget to remove the accepts_nested_attributes from Participation model, and make sure your migrations are correctly set up for match the associations you have. I hope have helped you.
Update
If you do not want to persist user information in the Participation then you can just add attribute accessors to your Participation model, and store information in your current_user in your create action:
#app/models/participation.rb
class Participation < ActiveRecord::Base
belongs_to :user
belongs_to :event
attr_accessor :first_name, :whatever_other_attribute # You can add as many attributes you need.
end
Then just update information of your current_user in your create action:
#app/controllers/participations_controller
def create
#participation = Participation.new(participation_params)
#participation.user = current_user
#participation.event = Event.find(params[:event_id])
current_user.update_attributes first_name: #participation.first_name
if #participation.save
redirect_to events_path, notice: "You are registered!"
else
render action: 'new'
end
end
This way you store the information in the current_user instead of Participation, also this way you can easily customize the different information you will ask in the participation form.
Thanks to #ssoulless for pointing me in to the final solution.
Ultimately I was able to update the controller action to associate the user and then use the participation_params to update the participation[:user].
# participations_controller.rb
...
def create
#participation = Participation.new(status: participation_params[:status])
#participation.mission_id = params[:mission_id]
current_user.update_attributes(participation_params[:user_attributes])
#participation.user = current_user
if #participation.save
redirect_to missions_path, notice: "You are registered!"
else
render action: 'new'
end
end
private
def participation_params
params.require(:participation)
.permit(:id, :status, user_attributes: [:id, :first_name])
end
I like this approach a bit more since it hides the user attributes in a private method. Also for anyone that this might help, when using accepts_nested_attributes_for in your model, you need to add it to the strong_params #permit parameters (Don't forget the ID!).
Related
Can't save params selected on select box.
Table users:
1id| |name|
1 CR7
2 Messi
Table ejecutives:
1id| |name|
1 Mourinho
2 Guardiola
Table user_ejecutives:
|id| |user_id| |ejecutive_id|
1 1 1
2 2 2
Controller users_controller.rb:
def new
#obj_user = User.new
end
def create
#user = User.new user_params
#user.save
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, user_ejecutive_ids: [])
end
Models:
#User.rb
has_many :ejecutives, :through => :user_ejecutives
has_many :user_ejecutives
has_and_belongs_to_many :user_ejecutives, class_name: "User", join_table: "user_ejecutives"#, foreign_key: :user_id, association_foreign_key: :ejecutive_id
#Ejecutive.rb
has_many :user_ejecutives
has_many :users, :through => :user_ejecutives
#UserEjecutive.rb
belongs_to :user
belongs_to :ejecutive
View new.html.erb:
<%= form_for #user do |f| %>
<%= form.text_field :name %>
<%= f.collection_select :user_ejecutive_ids, Ejecutive.all, :id, :name, multiple: true %>
<% end %>
View show.html.erb
<% #user.ejecutives.each do |ejecutive| %>
<%= ejecutive.name %></label>
<% end %>
I'm not getting results on the view show and it show on logs:
SystemStackError (stack level too deep):
If you're just trying to populate the join table (user_ejecutives), you'll want to populate the singular_colletion_ids method:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.require(:user).permit(:name, user_ejecutive_ids: [])
end
end
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.collection_select :user_ejecutive_ids, User.all, :id, :name, multiple: true %>
<%= f.submit %>
<% end %>
This will assign new user_ejecutives for each new #user you create.
PS User.all is valid in this instance as you're dealing with a new (uncreated) #user record, hence it won't appear in the db.
If you wanted to create new user_ejecutives with each new #user, you'll want to use accepts_nested_attributes_for, which I can explain if required.
Update
So your error is as follows:
Unpermitted parameter: user_ejecutive_ids
... you also have another error...
NoMethodError (undefined method `each' for nil:NilClass):
This is exactly why I don't like your code. Because it doesn't fit to convention, you've go to evaluate whether the params are present etc.
You'll need to use the controller code I posted - it will populate the other table for you, and fix this NilClass error.
--
Join Table
Your user_ejecutives table is a join table.
Your User model should have the following:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :user_ejecutives, class_name: "User", join_table: "user_ejecutives", foreign_key: :user_id, association_foreign_key: :ejecutive_id
end
You'll have to remove the id column from your user_ejecutives table (as per the definition here). The importance of this is that it gives you the ability to populate the singular_collection_ids method (in your case user_ejective_ids), as per my recommended code.
Try the following code.
params.require(:user).permit(:name, :user_ejecutives => [])
Hey, I think you have "ejecutive_id" column declared as integer but when loop through "user_ejecutives" you are getting each value as string, May be this is causing the issue, Kindly update your create action to below.
def create
obj_user = User.new(user_params)
if obj_user.save
params[:user_ejecutives].each do |ejecutive|
user_ejecutive = UserEjecutive.create(user_id: obj_user.id, ejecutive_id: ejecutive.to_i)
user_ejecutive.save
end
end
end
I will have an option were comment model can be used for a user to post in a person profile page and community page. Currently I'm working on community and would like some direction as I'm confused.
Current error I get is ActiveModel::ForbiddenAttributesError for my CommentsController#Create. Would like help if possible or help me point in the direction of fixing my mistake.
questions in regards to what view people are seeing comment is /communities/show
Models
User
has_one :profile
has_many :communities
has_many :comments, dependent: :destroy
Community
extend FriendlyId
friendly_id :title, use: [:slugged, :finders]
has_many :comments, dependent: :destroy
belongs_to :user
Comment
belongs_to :user
belongs_to :community
Routes
resources :communities do
resources :comments
end
Controllers
Communities
def show
#community = Community.friendly.find(params[:id])
#current_user = User.find(session[:user_id])
#comment = Comment.new
end
Comments
before_filter :load_community
def create
#comment = #community.comments.build(params[:comment])
#comment.user_id = current_user.id
if #comment.save
redirect_to :back
else
redirect_to "/"
end
# #comment = Comment.new(comment_params)
# #comment.user_id = session[:user_id]
# if #comment.save && #comment.community_id
# flash[:notice] = "Comment has been posted"
# else
# flash[:alert] = #comment.errors.full_messages
# end
end
private
def load_community
#community = Community.friendly.find(params[:community_id])
end
def comment_params
params.require(:comment).permit(:text, :user_id, :community_id, :profile_id)
end
Views
/communities/show
<%= render "profiles/index" %>
<h4><%= #community.title.capitalize! %></h4>
<%= #community.bio %>
<%= render "comments/new" %>
/comments/_new
<%= form_for ([#community, #comment]) do |f| %>
<%= f.text_area :text, placeholder: "Enter New Comment Here ...", :cols => 50, :rows => 3, :class => 'text_field_message', :id => 'new_comment' %>
<%= f.submit :class => 'new_comment_button' %>
<% end %>
Thank you everyone who helps explain where I'm making my mistake and also sorry in advance if I may need to ask what you might be requesting from me. For further questions please ask.
UPDATE
What I see in my console is
Started POST "/communities/dang/comments" for 127.0.0.1 at 2015-10-23 18:38:47 -0400
Processing by CommentsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"us8KNTLUZUdao13GK4OQId0YoUqf+CeLFIGjydnyWtI=", "comment"=> {"text"=>"www"}, "commit"=>"Create Comment", "community_id"=>"dang"}
Community Load (0.1ms) SELECT "communities".* FROM "communities" WHERE "communities"."slug" = 'dang' ORDER BY "communities"."id" ASC LIMIT 1
Completed 500 Internal Server Error in 11ms
ActiveModel::ForbiddenAttributesError (ActiveModel::ForbiddenAttributesError):
app/controllers/comments_controller.rb:17:in `create'
Okay.
ActiveModel::ForbiddenAttributesError for my CommentsController#Create
This basically means you're not permitting the required attributes in your create method.
This is what you need:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
#comment = #community.comments.new comment_params
#comment.save
end
private
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id)
end
end
You should read up on strong params to better understand how this works.
Polymorphic
You also have another issue which can be solved with a polymorphic association:
Simply, this allows you to associate a model with any number of others.
In your instance, where you can comment on users and communities, this functionality will serve well:
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, polymorphic: true
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :sent_comments, class_name: "Comment", foreign_key: :user_id
has_many :comments, as: :commentable
end
#app/models/community.rb
class Community < ActiveRecord::Base
has_many :comments, as: :commentable
end
This will allow you to perform the following:
#user = User.find params[:id]
#user.comments.new comment_params
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id) #-> current_user from Devise
end
This allows you to use #user.comments and #community.comments with a single association.
You'll have to migrate the commentable_id & commentable_type columns into your comments table, but after that the above code should work.
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 have two models Hotel and Address.
Relationships are:
class Hotel
belongs_to :user
has_one :address
accepts_nested_attributes_for :address
and
class Address
belongs_to :hotel
And I need to save in hotels table and in addresses table from one form.
The input form is simple:
<%= form_for(#hotel) do |f| %>
<%= f.text_field :title %>
......other hotel fields......
<%= f.fields_for :address do |o| %>
<%= o.text_field :country %>
......other address fields......
<% end %>
<% end %>
Hotels controller:
class HotelsController < ApplicationController
def new
#hotel = Hotel.new
end
def create
#hotel = current_user.hotels.build(hotel_params)
address = #hotel.address.build
if #hotel.save
flash[:success] = "Hotel created!"
redirect_to #hotel
else
render 'new'
end
end
But this code doesn't work.
ADD 1
Hotel_params:
private
def hotel_params
params.require(:hotel).permit(:title, :stars, :room, :price)
end
ADD 2
The main problem is I don't know how to render form properly. This ^^^ form doesn't even include adress fields (country, city etc.). But if in the line
<%= f.fields_for :address do |o| %>
I change :address to :hotel, I get address fields in the form, but of course nothing saves in :address table in this case. I don't understand the principle of saving in 2 tables from 1 form, I'm VERY sorry, I'm new to Rails...
You are using wrong method for appending your child with the parent.And also it is has_one relation,so you should use build_model not model.build.Your new and create methods should be like this
class HotelsController < ApplicationController
def new
#hotel = Hotel.new
#hotel.build_address #here
end
def create
#hotel = current_user.hotels.build(hotel_params)
if #hotel.save
flash[:success] = "Hotel created!"
redirect_to #hotel
else
render 'new'
end
end
Update
Your hotel_params method should look like this
def hotel_params
params.require(:hotel).permit(:title, :stars, :room, :price,address_attributes: [:country,:state,:city,:street])
end
You should not build address again
class HotelsController < ApplicationController
def new
#hotel = Hotel.new
end
def create
#hotel = current_user.hotels.build(hotel_params)
# address = #hotel.address.build
# the previous line should not be used
if #hotel.save
flash[:success] = "Hotel created!"
redirect_to #hotel
else
render 'new'
end
end
Bottom line here is you need to use the f.fields_for method correctly.
--
Controller
There are several things you need to do to get the method to work. Firstly, you need to build the associated object, then you need to be able to pass the data in the right way to your model:
#app/models/hotel.rb
Class Hotel < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
#app/controllers/hotels_controller.rb
Class HotelsController < ApplicationController
def new
#hotel = Hotel.new
#hotel.build_address #-> build_singular for singular assoc. plural.build for plural
end
def create
#hotel = Hotel.new(hotel_params)
#hotel.save
end
private
def hotel_params
params.require(:hotel).permit(:title, :stars, :room, :price, address_attributes: [:each, :address, :attribute])
end
end
This should work for you.
--
Form
Some tips for your form - if you're loading the form & not seeing the f.fields_for block showing, it basically means you've not set your ActiveRecord Model correctly (in the new action)
What I've written above (which is very similar to that written by Pavan) should get it working for you
I have a User who has many Accounts through a User_Accounts model. The User_Accounts model also tracks other information such as admin and billing access. Via the user edit form, I want to be able to edit the admin and billing boolean fields for the users current account.
user.rb
class User < ActiveRecord::Base
has_one :owned_account, class_name: 'Account', foreign_key: 'owner_id'
has_many :user_accounts
has_many :accounts, through: :user_accounts
accepts_nested_attributes_for :user_accounts
end
user_account.rb
class UserAccount < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
In the users controller, I specified which user_account I wanted to edit via the nested form and assigned to the #user_account instance variable.
users_controller.rb
def edit
#user = User.find(params[:id])
#user_account = #user.user_accounts.find_by_account_id(current_account)
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to #user, notice: 'User was successfully updated.'
else
render action: "edit"
end
end
private
def user_params
params.require(:user).permit(:name, :email, user_accounts_attributes: [:admin, :billing] )
end
user/edit.html.erb
<%= f.fields_for :user_accounts, #user_account do |o| %>
<%= o.check_box :admin, class: 'checkbox' %>
<% end %>
When I submit the change, it successfully saves the user record, but doesn't update the User_account record. It appears to be passing the following:
{"name"=>"Colin 21", "email"=>"mike21#example.com", "user_accounts_attributes"=>{"0"=>{"admin"=>"1"}}}
Id is required to edit an object via accepts_nested_attributes_for. Seems that the id attribute is not allowed through strong parameters.
Try changing the user_params method in 'users_controller.rb'
def user_params
params.require(:user).permit(:name, :email, user_accounts_attributes: [:id, :admin, :billing] )
end
You need to add a hidden field for user account's id field within fields_for part of the form too.