I have three models: Client, Car, and ParkingRate. Client has many cars, and car has many parking_rates. I have a form on the client page that creates a car associated with that client. What I don't know how to do is to add a field for parking_rate to that form, so that when a car is created for that client, a parking rate is also created for that car.
My code looks like:
client.rb
class Client < ActiveRecord::Base
has_many :cars, dependent: destroy
end
car.rb
class Car < ActiveRecord::Base
belongs_to :client
has_many :parking_rates
end
parking_rate.rb
class ParkingRate < ActiveRecord::Base
belongs_to :car
end
On the client page (client/:id), I have a form to create a car associated with that client, like this:
views/clients/show.html.erb:
<h1>Client information</h1>
... client info ...
<%= render 'cars/form' %>
views/cars/_form.html.erb:
<%= form_for([#client, #client.cars.build]) do |f| %>
<p>
<%= f.label :vehicle_id_number %><br>
<%= f.text_field :vehicle_id_number %>
</p>
<p>
<%= f.label :enter_date %><br>
<%= f.text_field :enter_date %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
The Clients and Cars controllers look like this:
clients_controller.rb:
class ClientsController < ApplicationController
def new
#client = Client.new
end
def create
#client = Client.new(client_params)
if #client.save
redirect_to #client
else
render 'new'
end
end
def show
#client = Client.find(params[:id])
end
def index
#clients = Client.all
end
def edit
#client = Client.find(params[:id])
end
def update
#client = Client.find(params[:id])
if #client.update(client_params)
redirect_to #client
else
render 'edit'
end
end
def destroy
#client = Client.find(params[:id])
#client.destroy
redirect_to clients_path
end
private
def client_params
params.require(:client).permit(:first_name, :last_name)
end
end
cars_controller.rb:
class CarsController < ApplicationController
def create
#client = Client.find(params[:client_id])
#car = #client.cars.create(car_params)
#parking_rate = #car.parking_rates.create(rate_params)
redirect_to client_path(#client)
end
def show
#client = Client.find(params[:client_id])
#car = Car.find(params[:id])
end
def edit
#client = Client.find(params[:client_id])
#car = Car.find(params[:id])
end
def update
#client = Client.find(params[:client_id])
#car = Car.find(params[:id])
#car.update(car_params)
redirect_to client_path(#client)
end
def destroy
#client = Client.find(params[:client_id])
#car = #client.cars.find(params[:id])
#car.destroy
redirect_to client_path(#client)
end
private
def car_params
params.require(:car).permit(:vehicle_id_number, :enter_date, :rate)
end
def rate_params
params.require(:parking_rate).permit(:rate)
end
end
With this I am able to add cars to a given client, but I would also like to add a parking_rate to a car on the same form. So right when I create a car using this form, I want to create an associated parking rate. The form_for helper uses a [#client, #client.comments.build] as the model object, so I am not sure how to reference the parking_rate model in the same form. I think the solution is to use a fields_for helper, what would be the model reference for that, and what would I need to add to the cars and client controllers?
In client.rb, add the line
accepts_nested_attributes_for :cars
Related
i am beginner on rails. I have product - brands list.
routes rb
Rails.application.routes.draw do
get 'welcome/index'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
resources :brands do
resources :products
end
root 'welcome#index'
end
Brand.rb
class Brand < ApplicationRecord
has_many :products, dependent: :destroy
validates :title, presence: true,
length: { minimum: 2 }
end
Product.rb
class Product < ApplicationRecord
belongs_to :brand
end
products.controller
class ProductsController < ApplicationController
#before_action :set_brand
skip_before_action :verify_authenticity_token
def new
if params[:brand_id]
#brand = Brand.find(params[:brand_id])
end
#product = Product.new
end
def edit
#brand = #product.brand
#product = Product.find(params[:id])
end
def update
#brand = Brand.find(params[:brand_id])
#product = Product.find(params[:id])
#product.update(product_params)
redirect_to brand_path(#brand)
end
def create
#brand = Brand.find(params[:brand_id])
#product = #brand.products.create(product_params)
redirect_to brand_path(#brand)
end
def destroy
#brand = Brand.find(params[:brand_id])
#product = #brand.products.find(params[:id])
#product.destroy
redirect_to brand_path(#brand)
end
def update
#brand = Brand.find(params[:brand_id])
#product = #brand.products.find(params[:id])
#product.destroy
end
helper_method :update
private
def product_params
params.require(:product).permit(:name)
end
def set_brand
#brand = Brand.find(params[:brand_id])
end
end
.../products/new.html.erb
<h1>Add a new product</h1>
<%= form_with(model: [ #brand, #brand.products.build ], local: true) do |form| %>
<p>
<%= form.label :name,"Product name: " %><br>
<%= form.text_field :name %>
</p>
<p>
</p>
<%= form.label :title,"Select a Brand" %>
<%= form.collection_select(:brand_id, Brand.all, :id, :title,{selected: #brand.id}) %>
<p>
<%= form.submit "Add a product" %>
</p>
<% end %>
new.html.erb picture
so i want to set brand_id from selected item on dropdown list. This case, i select first item for brand_id but i cant change the brand_id. How can i set brand_id which is selected from dropdown list ? and how can i save it.
You forgot to permit the parameter in product_params. Which should be:
params.require(:product).permit(:name, :brand_id)
Unpermitted parameters are ignored by the create and update methods.
Since you already have setup products as a nested resource you neither need or want that select. The brand_id param will be passed through the path (the action attribute of the form). The user would choose what brand he wants to add a product to by what link he clicked to get to the new form.
While you could add a select to the form its a major WTF moment when you send:
POST /brands/1/products, { products: { brand_id: 5 }}
And it ends up creating a product that does not belong to Brand 1. If the param from the form was blank you would also get a really strange result.
If really wanted a form where the user selects the "target" brand on the form itself you would create a non-nested respresentation:
<%= form_with(model: #product) do |f| %>
<%= f.collection_select :brand_id %>
<% end %>
class ProductsController < ApplicationController
# GET /products/new
def new
#product = Product.new
end
# POST /products
def create
#product = Product.new(product_params)
if #product.save
redirect_to #product
else
render :new
end
end
def product_params
params.require(:product).permit(:foo, :bar, :brand_id)
end
end
I keep getting a no method error. Why? How can I fix this?
NoMethodError in Articles#show
undefined method `photo' for #
I am using ruby on rails and I'm trying to use paperclip so i can upload photos on my app
part of my show file
<%= render #article.photos %> #source of error
<h3>Add a photo:</h3>
<%= render 'photos/form' %>
my photos controller
class PhotosController < ApplicationController
#Index action, photos gets listed in the order at which they were created
def index
#photos = Photo.order('created_at')
end
#New action for creating a new photo
def new
#photo = Photo.new
end
#Create action ensures that submitted photo gets created if it meets the requirements
def create
#article = Article.find(params[:article_id])
#photo = #article.photos.create(photo_params)
redirect_to article_path(#article)
end
def destroy
#article = Article.find(params[:article_id])
#photo = #article.photos.find(params[:id])
#photo.destroy
redirect_to article_path(#article)
end
private
#Permitted parameters when creating a photo. This is used for security reasons.
def photo_params
params.require(:photo).permit(:title, :image)
end
end
========= UPDATE =======
This is my
articles controller
class ArticlesController < ApplicationController
def new
#article = Article.new
end
def index
#articles = Article.all
end
def show
#article = Article.find(params[:id])
end
def create
#article = Article.new(article_params)
#article.save
redirect_to #article
end
def edit
#article = Article.find(params[:id])
end
def update
#article = Article.find(params[:id])
if #article.update(article_params)
redirect_to #article
else
render 'edit'
end
end
def destroy
#article = Article.find(params[:id])
#article.destroy
redirect_to articles_path
end
end
private
def article_params
params.require(:article).permit(:title, :text)
end
article model
class Article < ApplicationRecord
has_many :comments
end
I fixed it now, but now i have another no method error
undefined method `article_photos_path' for #<#:0x007f17f052d0a0>
Did you mean? article_path
<%= form_for([#article, #article.photos.build]) do |f| %> #source of error
<div class="form-group">
<%= f.label :image %>
<%= f.file_field :image, class: 'form-control'%>
</div>
<p>
<%= f.submit 'Upload Photo' %>
</p>
<% end %>
</p>
<% end %>
Being Photo another model so, you need to make the proper relationship:
class Article < ApplicationRecord
has_many :comments
has_many :photos
end
class Photo < ApplicationRecord
belongs_to :article
end
As I see in your photo_params, you don't have the article_id attribute then you must add it, running a migration:
$ rails g migration add_article_to_photos article:references
$ rails db:migrate
After that you should update them:
params.require(:photo).permit(:title, :image, :article_id)
I am building an app from an online tutorial. It tracks "Movies" and "Rentals." I am trying to set up the part where you create a new rental. When I submit the form, I get this error:
ActiveModel::ForbiddenAttributesError in RentalsController#create
Here is the full rentals controller:
class RentalsController < ApplicationController
def new
#movie = Movie.find(params[:id])
#rental = #movie.rentals.build
end
def create
#movie = Movie.find(params[:id])
#rental = #movie.rentals.build(params[:rental])
if #rental.save
redirect_to new_rental_path(:id => #movie.id)
end
end
end
It seems to take issue with this line in particular:
#rental = #movie.rentals.build(params[:rental])
Here is the Rental model:
class Rental < ApplicationRecord
has_one :movie
end
Here is the controller for Movies:
class MoviesController < ApplicationController
def new
#movie = Movie.new
#movies = Movie.all
end
def create
#movie = Movie.new(movie_params)
if #movie.save
redirect_to new_movie_path
end
end
private
def movie_params
params.require(:movie).permit(:title, :year)
end
end
Here is the Movie model:
class Movie < ApplicationRecord
has_many :rentals
end
Here are the routes:
Rails.application.routes.draw do
resources :movies, :rentals
root 'movies#new'
end
Here is the form:
<h1><%= #movie.title %></h1>
<%= form_for #rental, :url => {:action => :create, :id => #movie.id } do |r| %>
Borrowed on: <%= r.text_field :borrowed_on %><br />
Returned on: <%= r.text_field :returned_on %><br />
<br />
<%= r.button :submit %>
<% end %>
<br />
<%= link_to "back", new_movie_path %>
I'm not sure what is going on. From what I can tell, I am copying the tutorial exactly. Any help would be much appreciated!
You aren't using strong params for rentals, hence the ActiveModel::ForbiddenAttributesError error.
This should fix the error:
class RentalsController < ApplicationController
def new
#movie = Movie.find(params[:id])
#rental = #movie.rentals.build
end
def create
#movie = Movie.find(params[:id])
#rental = #movie.rentals.build(rental_params)
if #rental.save
redirect_to new_rental_path(:id => #movie.id)
end
end
private
def rental_params
params.require(:rental).permit(:borrowed_on, :rented_on)
end
end
On my homepage, I'm trying to set it up so when you click the "Get started" button, a website record is created, but also a page belonging to that website is created, and you're redirected to the page.
This is what I have so far. The website record is being created but the page is not being created.
Models
class Page < ApplicationRecord
belongs_to :website
end
class Website < ApplicationRecord
has_many :pages, :dependent => :destroy
accepts_nested_attributes_for :pages
end
Homepage controller
class MarketingPagesController < ApplicationController
def home
#website = Website.new
#website.pages.build
end
end
Website controller
class WebsitesController < ApplicationController
def create
#website = Website.new(creation_params)
if #website.save
redirect_to #website.Page.first
else
render :new
end
end
private
def shared_params
[:name]
end
def creation_params
params.require(:website).permit(*shared_params)
end
def update_params
params.require(:website).permit(*shared_params)
end
end
Page Controller
class PagesController < ApplicationController
def create
#page = Page.new(creation_params)
if #page.save
redirect_to #page
else
render :new
end
end
def show
#page = Page.find(params[:id])
#templates = Template.all
end
private
def shared_params
[:name, :website_id]
end
def creation_params
params.require(:page).permit(*shared_params)
end
def update_params
params.require(:page).permit(*shared_params)
end
end
Website form on homepage
<%= form_for #website do |f| %>
<%= f.hidden_field :name, value: "Untitled site" %>
<%= f.fields_for :pages do |builder| %>
<%= builder.hidden_field :name, value: "Untitled page" %>
<% end %>
<%= f.submit "Create Website" %>
<% end %>
You are using the association incorrectly
# Change
#website.Page.first
# to
#website.pages.first
Change this snippet in WebsiteController
if #website.save
redirect_to #website.pages.first
else
render :new
end
you have not white listed page params in website controller. modify your shared_params in website controller to :
def shared_params
[:name, pages_attributes: [:id, :name]]
end
and of course do changes suggested by #Deepak
I'm trying to build a small expense tracking app using Rails 4.1. Using devise for authorization. Expense and it's nested attribute, comments belong to a user. The associations are set up in the model and expenses are getting associated with the user. Here's the Expense controller:
class ExpensesController < ApplicationController
def new
#expense = Expense.new
#item = #expense.items.build
##comment = #expense.comments.build
end
def index
#expenses = Expense.all
##items = Item.where(:expense_id => #expense.id)
end
def show
#expense = Expense.find(params[:id])
#items = Item.where(:expense_id => #expense.id)
end
def create
#expense = current_user.expenses.new(expense_params)
respond_to do |format|
if #expense.save
ExpenseMailer.expense_submission(#expense).deliver
format.html { redirect_to #expense, notice: 'Expense Report Submitted.' }
format.json { render :show, status: :created, location: #expense }
else
format.html { render :new }
format.json { render json: #expense.errors, status: :unprocessable_entity }
end
end
end
def edit
#expense = Expense.find(params[:id])
end
def update
#expense = Expense.find(params[:id])
##comment = #expense.comments.build
if #expense.update(expense_params)
#if #comment.save
#ExpenseMailer.comments_added(#expense).deliver
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
#else
# flash[:notice] = "Expense Report Updated"
#redirect_to expenses_path
##end
else
render 'edit'
end
end
The form from where the comment attributes are built looks like:
<%= nested_form_for (#expense) do |f| %>
<div class="form-group">
<%= f.label :state %><br />
<%= f.select :state, Expense.states, :include_blank => false, class: "form-control" %>
</div>
<%= f.fields_for :comments, #expense.comments.build do |comment| %>
<div class="form-group">
<%= comment.label :comment%>
<%= comment.text_area :comment, class: "form-control" %>
</div>
<%= comment.hidden_field :commenter %>
<% end %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
The #comment.commenter = current_user isn't adding the current user id to the database. Should I include it in the expense controller somewhere?
You have to add:
#comment.commenter = current_user
below that if statement. Like this:
def create
#article = Expense.find(params[:expense_id])
if #comment = #expense.comments.create(comment_params)
#comment.commenter = current_user
#comment.save
ExpenseMailer.comments_added(#expense).deliver
redirect_to expenses_path
end
end
And then save the comment again. In your current code you're overwriting the #comment object with the newly created object by doing:
#comment = #expense.comments.create(comment_params)
but you haven't set the commenter on that new object anywhere yet.
Model
I just tried to create better code for your strong params, but I couldn't work out how to include the param in your nested attributes
I would therefore recommend using the inverse_of: method in your Comment model to get it sorted properly:
#app/models/expense.rb
Class Expense < ActiveRecord::Base
belongs_to :user
has_many :comments, inverse_of: :expense
accepts_nested_attributes_for :comments
end
#app/models/comment.rb
Class Comment < ActiveRecord::Base
belongs_to :expense, inverse_of: :comments
before_create :populate_expense, on: :create
private
def populate_expense
self.commenter_id = self.expense.user_id
end
end
This should work if you're populating the comments from the accepts_nested_attributes_for directive
Comments
I don't understand why you've created two create actions for both your expenses and comments controllers - the controller action is meant to be independent of the Model
What I'm trying to say is that if you think the comments#create controller action will be invoked by your nested attribute creation, you'd be mistaken - it is only invoked when you send a request to it through the Rails router :)
If you're creating Comments and Expenses separately, you'll be able to use these two different actions; but they won't be invoked by each other. Only Model methods can be invoked by the controller (you shouldn't be calling other controller methods)
If you wanted to create a Comment from the expenses#show page, here's how you'd set it up:
#config/routes.rb
resources :expenses do
resources :comments #-> domain.com/expenses/:expense_id/comments/new
end
#app/controllers/expenses_controller.rb
Class CommentsController < ApplicationController
def new
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new
end
def create
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new(comment_params)
#comment.save
end
private
def comment_params
params.require(:comment).permit(:comment, :params).merge(commenter_id: current_user.id)
end
end
This will work if you wanted to create a comment from the expenses#show page. If you do this, you need to ensure you are calling the comments#new / comments#create actions, rather than those of the expenses controller