My problem is connected with my previous problem. It was solved by using nested routing. But there was different idea without using nested resources. I tried it as an exercise. But when I submit data, ActiveRecord::RecordNotFound in SentencesController#create shows up.
Rails.application.routes.draw do
root 'stories#index'
get 'stories/show'
get 'stories/new'
post 'stories/:story_id', to: 'sentences#create'
resources :stories
resources :sentences, only: [:create]
end
shared/_sentence_form.html.erb is part of story/show_form.html.erb
<%= form_for(#sentence) do |f| %>
<%= hidden_field_tag :story_id, value: #story.id %>
<%= f.text_area :content, placeholder: "Compose new sentence..." %>
<%= f.submit "Save"%>
<% end %>
SentencesController
class SentencesController < ApplicationController
before_action :sentence_params
before_action :find_story
def create
#sentence = find_story.sentences.build(sentence_params)
if #sentence.save
flash[:success] = "You wrote the continuation!"
redirect_to root_url
else
flash[:danger] = "I did not save your words!"
redirect_to "#"
end
end
private
def sentence_params
params.permit(:content, :story_id)
end
def find_story
#story = Story.find(params[:story_id])
end
end
and model:
class Sentence < ApplicationRecord
belongs_to :story
validates :story_id, presence: true
validates :content, presence: true, length: { maximum: 150 }
end
I tried many combinations in controller and view. When I put some information in the text_area and click submit they don't save.
The param is nested:
def find_story
#story = Story.find(params[:sentance][:story_id])
end
But nesting the route is a better RESTful design anyways.
The route POST /stories/:story_id/sentances makes it very clear what the action does.
resources :stories do
resources :sentences, only: [:create]
end
<%= form_for([#story, #sentence]) do |f| %>
<%= f.text_area :content, placeholder: "Compose new sentence..." %>
<%= f.submit "Save"%>
<% end %>
This will properly pass params[:story_id] as a segment of the URL.
I am not sure that's a solution to your problem, but I believe that
def sentence_params
params.permit(:content, :story_id)
end
needs a :sentence in there somewhere. Your form is a form_for #sentence so I guess your params are params[:sentence][:content] or similar.
I believe your error happens in the line #story = Story.find(params[:story_id]), right? Make sure that the #story variable is present in your form and that the record can be found.
Related
I have Categories (Parents) within which are listed Products (Children).
I want to be able to create a new Product directly from the navbar, anywhere in the app and then, during the creation, assign it to a Category.
However, I get the present error:
NoMethodError in Products#new
Showing /Users/istvanlazar/Mobily/app/views/products/new.html.erb where line #9 raised:
undefined method `products_path' for #<#<Class:0x00007febaa5aec98>:0x00007febae0f9e38>
Did you mean? product_show_path
## product_show_path is a custom controller that has nothing to do with this one,
enabling show and clean redirection from newest_products index bypassing categories nesting.
Extracted source (around line #9):
9 <%= form_for [#product] do |f| %>
10 <div class="form-styling">
11 <div>
12 <%= f.label :name %>
My App works as such:
Models
class Category < ApplicationRecord
has_many :products, inverse_of: :category
accepts_nested_attributes_for :products
validates :name, presence: true
end
class Product < ApplicationRecord
belongs_to :user, optional: true
belongs_to :category, inverse_of: :products
validates :category, presence: true
end
Routes
get 'products/new', to: 'products#new', as: 'new_product'
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :edit]
# resources :products, only: [:index, :show, :new, :edit]
end
Controllers
class ProductsController < ApplicationController
before_action :set_category, except: :new
def index
#products = Product.all
#products = policy_scope(#category.products).order(created_at: :desc)
end
def show
#product = Product.find(params[:id])
end
def new
#product = Product.new
#product.user = current_user
end
def create
#product = Product.new(product_params)
#product.user = current_user
if #product.save!
redirect_to category_product_path(#category, #product), notice: "Product has been successfully added to our database"
else
render :new
end
end
private
def set_category
#category = Category.find(params[:category_id])
end
def product_params
params.require(:product).permit(:name, :price, :description, :category_id, :user, :id)
end
end
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def show
#category = Category.find(params[:id])
end
def new
# Non-existant created in seeds.rb
end
def create
# idem
end
def edit
# idem
end
def update
# idem
end
def destroy
# idem
end
private
def category_params
params.require(:category).permit(:name, :id)
end
end
Views
# In shared/navbar.html.erb:
<nav>
<ul>
<li>Some Link</li>
<li>Another Link</li>
<li><%= link_to "Create", new_product_path %></li>
</ul>
</nav>
# In products/new.html.erb:
<%= form_for [#product] do |f| %>
<div class="form-styling">
<div>
<%= f.label :name %>
<%= f.text_field :name, required: true, placeholder: "Enter product name" %>
<%= f.label :price %>
<%= f.number_field :price, required: true %>
</div>
<div>
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div>
<%= f.label :category %>
<%= f.collection_select :category_id, Category.order(:name), :id, :name, {prompt: 'Select a Category'}, required: true %>
</div>
<div>
<%= f.submit %>
</div>
</div>
<% end %>
Do you have any idea of where it went wrong? Or is it impossible to Create a Child before assigning it to a Parent..?
Thanks in advance.
Best,
Ist
You haven't defined any route to handle your new product form's POST. You've defined the new_product path, but this arrangement is breaking Rails' conventions and you're not providing a work-around.
You could define another custom route, e.g. post 'products', to: 'products#create', as: 'create_new_product' and then set that in your form like form_for #product, url: create_new_product_path do |f|.
However, you should consider changing the structure so that product routes are not nested under categories. Think twice before breaking conventions this way.
Edit: I misread the intention, ignore this. Go with Jeremy Weather's answer.
Or is it impossible to Create a Child before assigning it to a Parent..?
With the way you have your relationships and validations set up: yes, it is. A Product requires a Category, through the validates :category, presence: true. And if you're on Rails 5+, the association will already be required by default (meaning that validates is actually redundant). Meaning if you try to create a Product without a Category, it will fail, and the Product will not be saved to the database.
With that constraint in place, here's what I recommend trying:
Remove the custom /products/new route
remove get 'products/new', to: 'products#new', as: 'new_product'
Add :new and :create routes to allow product creation nested under categories
Add :create and :new, to the :only array to the resources :products nested under resources :categories
Move the file views/products/new.html.erb to views/categories/products/new.html.erb (creating a new categories directory in views if necessary).
In that file, alter the form_for so that the form is POSTing to the nested :create route:
form_for [#category, #product] do |f|
Visit the URL /categories/[insert some Category ID here]/products/new, filling out some data, and see if that works.
Your routes.rb should look like this:
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :new, :create, :edit]
end
My goal is to when adding a new product with the new product form, to have an input where one can add a list of emails separated by a space. The list of emails in this string field would be saved as an array of emails in the email_list array attribute of the Product model. This way each product has many emails. (later an email will be sent to these users to fill out questionaire, once a user fills it out there name will be taken off this list and put on completed_email_list array.
I am relatively new to rails, and have a few questions regarding implementing this. I am using postgresql, which from my understanding I do not need to serialize the model for array format because of this. Below is what I have tried so far to implement this. These may show fundamental flaws in my thinking of how everything works.
My first thinking was that I can in my controllers create action first take params[:email].split and save that directly into the email_list attribute (#product.email_list = params[:email].split. It turns out that params[:email] is always nil. Why is this? (this is a basic misunderstanding I have)(I put :email as accepted param).
After spending a long time trying to figure this out, I tried the following which it seems works, but I feel this is probably not the best way to do it (in the code below), which involves creating ANOTHER attribute of string called email, and then splitting it and saving it in the email_list array :
#product.email_list = #product.email.split
What is the best way to actually implement this? someone can clear my thinking on this I would be very grateful.
Cheers
Products.new View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.input :email %>
<%= f.button :submit %>
<%end %>
Products Controller
class ProductsController < ApplicationController
before_action :find_product, only: [:show, :edit, :update, :destroy]
def index
if params[:category].blank?
#products= Product.all.order("created_at DESC")
else
#category_id=Category.find_by(name: params[:category]).id
#products= Product.where(:category_id => #category_id).order("created_at DESC")
end
end
def new
#product=current_user.products.build
#categories= Category.all.map{|c| [c.name, c.id]}
end
def show
end
def edit
#categories= Category.all.map{|c| [c.name, c.id]}
end
def update
#product.category_id = params[:category_id]
if #product.update(product_params)
redirect_to product_path(#product)
else
render 'new'
end
end
def destroy
#product.destroy
redirect_to root_path
end
def create
#product=current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = #product.email.split
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:product).permit(:title, :description, :category_id, :video, :thumbnail,:email, :email_list)
end
def find_product
#product = Product.find(params[:id])
end
end
To solve your original issue
#product.email_list = params[:email].split. It turns out that params[:email] is always nil
:email is a sub key of :product hash, so it should be:
#product.email_list = params[:product][:email].split
Demo:
params = ActionController::Parameters.new(product: { email: "first#email.com last#email.com" })
params[:email] # => nil
params[:product][:email] # => "first#email.com last#email.com"
I'd say that what you have is perfectly fine, except for the additional dance that you're doing in #product.email_list=#product.email.split, which seems weird.
Instead, I'd have an emails param in the form and an #emails= method in the model (rather than email and #email=):
def emails=(val)
self.email_list = val.split
end
Alternatively, you could do that in the controller rather than having the above convenience #emails= method, similar to the way you're handling the category_id:
#product = current_user.products.build(product_params)
#product.category_id = params[:category_id]
#product.email_list = product_params[:emails].split
Because you need validations on your emails and to make it cleaner I would create an email table, make Product table accept Email attribues and use cocoon gem to have a nice dynamic nested form with multiple emails inputs.
1) models
class Product < ActiveRecord::Base
has_many :emails, dependent: :destroy
accepts_nested_attributes_for :emails, reject_if: :all_blank, allow_destroy: true
end
class Email < ActiveRecord::Base
belong_to :product
validates :address, presence: true
end
2) Controller
class ProductsController < ApplicationController
def new
#product = current_user.products.build
end
def create
#product = current_user.products.build(product_params)
if #product.save
redirect_to root_path
else
render 'new'
end
end
private
def product_params
params.require(:project).permit(:title, :description, :category_id, :video, :thumbnail, emails_attributes: [:id, :address, :_destroy])
end
end
3) View
<%= simple_form_for #product do |f| %>
<%= f.input :title, label:"Product title" %>
<%= f.input :description %>
<%= f.association :category %>
<div id="emails">
<%= f.simple_fields_for :emails do |email| %>
<%= render 'emails_fields', f: email %>
<div class="links">
<%= link_to_add_association 'add email', f, :emails %>
</div>
<%= end %>
</div>
<%= f.button :submit %>
<% end %>
In your _emails_fields partial:
<div class="nested-fields">
<%= f.input :address %>
<%= link_to_remove_association "Remove email", f %>
</div>
Then setup cocoon's gem and javascript and you'll be good.
Reference: https://github.com/nathanvda/cocoon
When going on the view page, the error "param is missing or the value is empty: restaurant" is displayed.
- I solve the problem by deleting "require(:restaurant)" but don't really understand the trick...do you ?
- I am looking for a solution that allows me to keep the "require(:restaurant)... Any Idea ?
restaurants_controller.rb
class RestaurantsController < ApplicationController
before_action :set_restaurant, only: [:show, :edit, :update]
def index
#restaurants = Restaurant.all
end
def show
end
def new
#restaurant = Restaurant.new(params.permit(:name, :address, :category))
end
def create
#restaurant = Restaurant.new(restaurant_params)
#restaurant.save
redirect_to restaurant_path(#restaurant)
end
private
def set_restaurant
#restaurant = Restaurant.find(params[:id])
end
def restaurant_params
params.require(:restaurant).permit(:name, :address, :category)
end
end
new.html.erb
<%= simple_form_for(#restaurant) do |f| %>
<% if #restaurant.errors.any? %>
<div class="errors-container">
<ul>
<% #restaurant.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.input :name %>
<%= f.input :address %>
<%= f.input :category %>
<%= f.submit "add a resto", class: "btn btn-primary" %>
<% end %>
restaurant.rb
class Restaurant < ApplicationRecord
validates :name, :address, :category, presence: true
validates :category, inclusion: { in: ["chinese", "italian", "japanese", "french", "belgian"]}
has_many :reviews, dependent: :destroy
end
routes
Rails.application.routes.draw do
resources :restaurants, only: [:new, :create, :show, :index] do
resources :reviews, only: [ :new, :create ]
end
end
The inputs of your form should be named like this:
restaurant[name]
restaurant[address]
restaurant[category]
It could be that it is something like is:
name
address
category
You can debug your params by inspecting it.
You could do something like this in your restaurant_params in the controller:
raise params.inspect
Or open your debug console of the browser and see what is posted.
And as mentioned in the comments you can use the restaurant_params in your new method but I don't think that fixes you issue it is more likely that the form is the problem.
# instead of
Restaurant.new(params.permit(:name, :address, :category))
# do
Restaurant.new(restaurant_params)
I assume you're trying to auto-populate the form on your new view with some passed attributes.
Your restaurant#new is looking for a params[:restaurant] hash to create a new Restaurant object and can't find it. Look at how you're calling the new action and what parameters are being passed -- my guess is that you're not passing any or you're passing them in the root params hash like params[:name], params[:address] instead of params[:restaurant] => { :name => 'name'}.
Most new actions are a GET so if you're using inline URL parameters put them in like http://address.com/restaurant/new?restaurant[name]=Bistro&restaurant[category]=burgers
In restaurants_controller.rb, we can declare a method for show like:
def show
#restaurant = Restaurant.find(params[:id])
end
In the View Section where we want to link with a button, we can paste
<td><%= link_to "Show", restaurant %> </td>
I hope it will work
Hi I am using gem "nested_form" and included has_many association in my app sample code is :
class Question < ActiveRecord::Base
has_many :choices
accepts_nested_attributes_for :choices
end
and in my controller have included this :
class QuestionsController < ApplicationController
before_action :set_questions, only: [:edit, :update]
def edit
end
def update
if #question.update_attributes(question_params)
redirect_to questions_path
else
render :action => :edit
end
end
private
def set_questions
#question = Question.where(:id => params[:id]).first
end
def question_params
params.require(:question).permit(:content,
choices_attributes: [:option, :is_correct,
:question_id])
end
end
and in edit.html.erb
<%= nested_form_for #question do |f|%>
<%= f.label :content %>
<%= f.text_field :content %>
<%= f.fields_for :choices do |c| %>
<%= c.label :option %>
<%= c.text_field :option %>
<%= c.check_box :is_correct%>
<%= c.label :is_correct %>
<% end %>
<%= f.link_to_add "Add Choices", :choices%>
</br>
<%= f.submit %>
<% end %>
so in edit it adds choices even they are present and I have not even edit/add any of choices.
If I already have 3 choices with respect to question_id=1 so at the time of edit I have not edited any of choices nor I have added any new for that question_id but then too at the time of submit it creates 3 more choices. It gives this params on submit
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"jTLaIz0BdKbSZgnMl4T2GhZyYbKvo0JG2VD8e1zbvQGp6ILyKqLOZy19QvZrXhVGr5OClcwibWL0HJwIAGJ/rQ==",
"question"=>{"content"=>"Business logic is defined in ?",
"choices_attributes"=>{"0"=>{"option"=>"Model", "is_correct"=>"1",
"id"=>"36"}, "1"=>{"option"=>"view", "is_correct"=>"0", "id"=>"37"},
"2"=>{"option"=>"controller", "is_correct"=>"0", "id"=>"38"},
"3"=>{"option"=>"helpers", "is_correct"=>"0", "id"=>"39"}}},
"commit"=>"Update Question", "id"=>"10"}
Please guide me how to solve this. Thanks in advance.
The problem is in your question_params. You have to add :id for edit/update to work correctly else it will create new records on every successful submit.
def question_params
params.require(:question).permit(:id, :content, choices_attributes: [:id, :option, :is_correct, :question_id])
end
It might be happening because you haven't permitted id of choice in choices_attributes.
nested_form treats every choice attributes as creating new record on submit if it don't contain id.
You rails console must be giving unpermitted parameter as :id because you haven't passed id to the update_attributes thats why it is creating new object , all you need to do is
def question_params
params.require(:question).permit(:id, :content, choices_attributes: [:id, :option, :is_correct, :question_id])
end
I am following Michael Hartl's Rails Tutorial and have completed the part about creating microposts. I was wondering if anyone have an idea about how to make the micropost form responsive to a hyperlink. For example, when a user types in "Visit our HTML tutorial" in the micropost, I want the link to active. Any help would be appreciated.
micropost_controller.rb
class MicropostsController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
def destroy
#micropost.destroy
redirect_to root_url
end
private
def micropost_params
params.require(:micropost).permit(:html)
end
def correct_user
#micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if #micropost.nil?
end
end
micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
default_scope -> { order('created_at DESC') } validates :content,
presence: true, length: { maximum: 140 } validates :user_id,
presence: true end
...
end
micropost_form.html.erb
<%= form_for(#micropost) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
You can use the sanitize helper method and pass in the anchor (a) tag as the only allowable tag. You don't use it when they create the post, you use it when you are showing the micropost in the view
app/views/microposts/show.html.erb
<%= sanitize micropost.content, tags: ['a'] %>
(I don't know exactly how you are showing the content of a micropost, but this should give you an idea)
This is safer than other options like html_safe because you can actually control which html tags you will allow the user to be able to input.