I have Project and ProjectSetting models with following associations:
class Project < ActiveRecord::Base
has_one :project_setting
end
class ProjectSetting < ActiveRecord::Base
belongs_to :project
end
In projects_controller I have:
def show
#project = Project.find(params[:id])
#project_setting = #project.project_setting
end
So I'm using #project_setting form in #project show page and I need to update #project_setting from this page.
In project_settings_controller I have:
def update
#project = Project.find(params[:id]) #problem is here
#project_setting = #project.project_setting
if #project_setting.update_attributes(params[:project_setting])
respond_to do |format|
format.html { redirect_to project_path(#project) }
format.js
end
end
end
But #project variables in these controllers aren't the same:
In projects_controller#show it is Project with ID 26 and in project_settings_controller#update it finds Project with ID 1
So I need to pass #project variable from projects_controller#show to project_settings_controller#update.
Thanks for any help!
In your show.html.erb you can pass the variables back to any controller. For example
<%= link_to "Update project setting",
:controller => "project_settings",
:action => "update",
:project => #project %>
will send the parameter "project" filled with the #project variable.
If you are in a form tag, you can send the variable with a hidden field tag:
<% hidden_field_tag("project", #project) %>
I hope, this helps.
params[:id] in project_settings_controller contained #project_setting.id
If you want to get #project.id from params, you should to write in routes.rb nested path:
resources :projects do
resources :project_settings
end
And then project.id is available in params[:project_id].
Example in rails_guides
Related
Im trying to add service details for a vehicle. On the show page for my vehicle I've put a button which links to add new service details
<%= link_to 'Add Service Details', {:controller => :services, :action => :new, :vehicle_id => #vehicle.id}, {class: "btn btn-secondary"} %>
The url will show the vehicle_id like the following
http://localhost:3000/services/new?vehicle_id=3
on the _form.html.erb for services I've got
<div class="field">
<%= form.label :vehicle_id %>
<%= form.text_field :vehicle_id, id: :service_vehicle_id %>
</div>
In the controller I've got
def create
#vehicle = Vehicle.find(params[:vehicle_id])
#service = #vehicle.service.create(service_params)
redirect_to #service, notice: 'Service was successfully created.'
end
and
def new
#vehicle = Vehicle.find(params[:vehicle_id])
#service = Service.new
end
In my model I've defined the relationship as
Vehicle model
has_many :service
Service model
belongs_to :vehicle
My routes file looks like
resources :vehicles
resources :services
Can someone please help me use the vehicle_id and save it as a foreign key. I'm not sure if the above is the right approach but I'd like it so when the user views a vehicle they've got a button which allows them to add service details.
The user go to
http://localhost:3000/services/new?vehicle_id=3
in your controller the following code run
def new
#Serch for a vehicle
#vehicle = Vehicle.find(params[:vehicle_id])
# Create a Service object with no vehicle_id set
#service = Service.new
end
Like vehicle_id is not set then
<%= form.text_field :vehicle_id, id: :service_vehicle_id %>
should genarate a text field without value, when you submit the form
def create
# Here must throw an error
#vehicle = Vehicle.find(params[:vehicle_id])
#service = #vehicle.service.create(service_params)
redirect_to #service, notice: 'Service was successfully created.'
end
Go to the new action set the vehicle id
def new
#Serch for a vehicle
#vehicle = Vehicle.find(params[:vehicle_id])
# Create a Service object with no vehicle_id set
#service = Service.new
# Add vehicle id to service
#service.vehicle_id = #vehicle.id
end
If what you want is a form that you can place on show action of a vehicule to create a new service than what you want is just your run of the mill nested resource:
resources :vehicules do
resources :services, shallow: true
end
This will setup the routes so that you create a new service by POST'ing to /vehicules/:vehicule_id/services. Note that this puts the parent id in the path so that you don't need to send it as a hidden input.
For the form you just do:
<%= form_for([#vehicule, #service || Service.new]) do |f| %>
# inputs go here
<% end %>
# or with Rails 5 'form_with'
# form_with(model: #vehicule, url: [#vehicule, #service || Service.new])
You then setup the controller:
class ServicesController < ApplicationController
before_action :set_vehicule, only: [:new, :create, :index]
def new
#service = #vehicule.services.new
end
def create
#service = #vehicule.services.new(service_params)
if #service.save
# ...
else
# ...
end
end
def index
#services = #vehicule.services
end
# ...
private
def set_vehicule
#vehicule = Vehicule.find(params[:vehicule_id])
end
def service_params
# ...
end
end
I have researched similar questions however I don't feel link they have addressed my particular issue:
Rails form_for results in POST instead of PUT when trying to edit
form_for with nested resources
I'm a novice with Rails (using Rails 4.2.5) an am attempting my first application. My issue is two fold: (1) When a user goes to edit a user story the fields of the form do not populate with previously inputted data (2) When the form is resubmitted, a new entry is created, opposed to editing the old data.
I have a feeling that my form_for for user_stories/edit.html.erb is the issue. When I take out the .build method from the form I get the following error message:
undefined method `to_key' for #UserStory::ActiveRecord_Associations_CollectionProxy:0x007f456a759138>
The projects/_form.html.erb for my project's view does not have the .build method and functions correctly. However the only way I can get the `user_stories/_form.html.erb form to work is if I attach the build method.
Here is my code:
user_story.rb
class UserStory < ActiveRecord::Base
belongs_to :project
belongs_to :user
include RankedModel
ranks :row_order
end
project.rb
class Project < ActiveRecord::Base
has_many :user_stories
belongs_to :user
end
routes.rb
Rails.application.routes.draw do
devise_for :users
resources :projects do
resources :user_stories
end
end
resources :user_stories do
post :update_row_order, on: :collection
end
root 'welcome#index'
end
user_stories/_form.html.erb
<%= form_for([#project, #user_story.build]) do |f| %>
<div class="form-group">
<p>As a ...</p>
<%= f.text_field :param1, placeholder: "type of user", class: "form-control" %>
</div>
<div class="form-group">
<p>I want ...</p>
<%= f.text_field :param2, placeholder: "desired functionality", class: "form-control" %>
</div>
<div class="form-group">
<p>so that...</p>
<%= f.text_field :param3, placeholder: "reason for desired functionality", class: "form-control" %>
</div>
<div class="actions">
<%= f.submit class: "btn btn-primary" %>
</div>
<% end %>
user_stories_controller.rb
class UserStoriesController < ApplicationController
before_action :set_project
before_action :set_user_story, except: [:create]
def index
#user_story = #project.user_stories.rank(:row_order).all
end
def update_row_order
#user_story.row_order_position = user_story_params[:row_order_position]
#user_story.save
render nothing:true # this is a POST action, updates sent via AJAX, no view rendered
end
def create
#user_story = #project.user_stories.create(user_story_params)
redirect_to #project
end
def new
end
def destroy
if #user_story.destroy
flash[:success] = "User story deleted"
else
flash[:error] = "User story could not be deletd"
end
redirect_to #project
end
def complete
user_story.update_attribute(completed_at, Time.now)
redirect_to #project, notice: "User story completed functionality complete"
end
def update
respond_to do |format|
if #project.user_stories.update(#project, user_story_params)
format.html { redirect_to project_path(#project), notice: 'User story was successfully updated.' }
format.json { render :show, status: :ok, location: #user_story }
else
format.html { render :edit }
format.json { render json: #user_story.errors, status: :unprocessable_entity }
end
end
end
def edit
#project = Project.find(params[:project_id])
#user_story = #project.user_stories(params[:id])
end
def show
end
private
def set_project
#project = Project.find(params[:project_id])
end
def set_user_story
#user_story = #project.user_stories(params[:id])
end
def user_story_params
params[:user_story].permit(:param1, :param2, :param3, :row_order_position)
end
end
There are just a few changes needed (tweaks, really), and I'll go through them top-to-bottom.
1) before_action :set_user_story
This will use the param[:id] to find the proper #user_story model object and automatically make it available to the proper methods. In this case it's being excepted for :create, but should also exclude other methods that don't have an :id in the route. Use this instead:
before_action :set_user_story, except: [:index, :new, :create]
This will solve (or prevent) some annoying and persistent ActiveRecord failures.
2) The index action
In this method, the name of the variable is non-standard by Rails naming conventions. The variable is currently singular, but represents a list of UserAction model object, which typically uses a plural name. Use this, instead:
def index
#user_stories = #project.user_stories.rank(:row_order).all
end
This change will cause a break in the app/views/user_stories/index.html.erb view, where any use of the #user_story variable would need to be changed to #user_stories. Keeping with naming conventions has many immediate and long-term benefits, so it's worth making the extra effort to change this to be consistent.
Note: the index action typically doesn't have a singular model object to work with, as this action is used to provide a list of the model objects.
3) The new action
The new action is used to create and initialize a new model object for editing. As the before_action :set_user_story is no longer being called for the new action, the #user_story model object has to be created here. This code will do that correctly:
def new
#user_story = UserStory.new
#user_story.project = #project
# Set other important default values for display now
end
And at this point, you should be able to successfully create a new UserStory model object, ready to be edited by the user.
4) The edit action
As the before_action :set_user_story handler is already being called for the edit action, there's no need to query for #user_story from within the body of the edit action; that line can be removed:
def edit
#project = Project.find(params[:project_id])
end
This will actually fix the original issue that was reported, as this form of find will (unfortunately for this situation) return multiple records, which means that you get a collection back, and not a single record. This is the actual cause of this error message:
undefined method `to_key' for #UserStory::ActiveRecord_Associations_CollectionProxy:0x007f456a759138>
Assigning the #user_story within the edit action overwrote the value that had previously been assigned from the before_action handler, and replaced it with an improper query result.
5) The complete action
The complete action is a custom member action, which means that it depends on the :id, just like many of the other actions. The code is almost correct, except that the user_story variable used within the body of the method is actually missing the #; this is originally retrieved by the before_action handler.
def complete
#user_story.update_attribute(completed_at, Time.now)
redirect_to #project, notice: "User story completed functionality complete"
end
It's likely that this method had not been called yet during testing, as the edit action was an upstream test that failed. This should work when you get to testing this method.
6) Teh codez
Changing those few details will finalize the UserStoriesController, which was in pretty great shape to begin with. Adding in those changes, this is the final controller code:
class UserStoriesController < ApplicationController
before_action :set_project
before_action :set_user_story, except: [:index, :new, :create]
def index
#user_stories = #project.user_stories.rank(:row_order).all
end
def update_row_order
#user_story.row_order_position = user_story_params[:row_order_position]
#user_story.save
render nothing:true # this is a POST action, updates sent via AJAX, no view rendered
end
def create
#user_story = #project.user_stories.create(user_story_params)
redirect_to #project
end
def new
#user_story = UserStory.new
#user_story.project = #project
# Set other important default values for display now
end
def destroy
if #user_story.destroy
flash[:success] = "User story deleted"
else
flash[:error] = "User story could not be deleted"
end
redirect_to #project
end
def complete
#user_story.update_attribute(completed_at, Time.now)
redirect_to #project, notice: "User story completed functionality complete"
end
def update
respond_to do |format|
if #project.user_stories.update(#project, user_story_params)
format.html { redirect_to project_path(#project), notice: 'User story was successfully updated.' }
format.json { render :show, status: :ok, location: #user_story }
else
format.html { render :edit }
format.json { render json: #user_story.errors, status: :unprocessable_entity }
end
end
end
def edit
#project = Project.find(params[:project_id])
end
def show
end
private
def set_project
#project = Project.find(params[:project_id])
end
def set_user_story
#user_story = #project.user_stories(params[:id])
end
def user_story_params
params[:user_story].permit(:param1, :param2, :param3, :row_order_position)
end
end
I have a very straight-forward task to fulfil --- just to be able to write comments under posts and if the comments fail validation display error messages on the page.
My comment model uses a gem called Acts_as_commentable_with_threading, which creates a comment model after I installed.
On my post page, the logic goes like this:
Posts#show => display post and a form to enter comments => after the comment is entered, redisplay the Post#show page which has the new comment if it passes validation, otherwise display the error messages above the form.
However with my current code I can't display error messages if the comment validation fails. I think it is because when I redisplay the page it builds a new comment so the old one was erased. But I don't know how to make it work.
My codes are like this:
Comment.rb:
class Comment < ActiveRecord::Base
include Humanizer
require_human_on :create
acts_as_nested_set :scope => [:commentable_id, :commentable_type]
validates :body, :presence => true
validates :first_name, :presence => true
validates :last_name, :presence => true
# NOTE: install the acts_as_votable plugin if you
# want user to vote on the quality of comments.
#acts_as_votable
belongs_to :commentable, :polymorphic => true
# NOTE: Comments belong to a user
belongs_to :user
# Helper class method that allows you to build a comment
# by passing a commentable object, a user (could be nil), and comment text
# example in readme
def self.build_from(obj, user_id, comment, first_name, last_name)
new \
:commentable => obj,
:body => comment,
:user_id => user_id,
:first_name => first_name,
:last_name => last_name
end
end
PostController.rb:
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
def show
#post = Post.friendly.find(params[:id])
#new_comment = Comment.build_from(#post, nil, "", "", "")
end
end
CommentsController:
class CommentsController < ApplicationController
def create
#comment = build_comment(comment_params)
respond_to do |format|
if #comment.save
make_child_comment
format.html
format.json { redirect_to(:back, :notice => 'Comment was successfully added.')}
else
format.html
format.json { redirect_to(:back, :flash => {:error => #comment.errors}) }
end
end
end
private
def comment_params
params.require(:comment).permit(:user, :first_name, :last_name, :body, :commentable_id, :commentable_type, :comment_id,
:humanizer_answer, :humanizer_question_id)
end
def commentable_type
comment_params[:commentable_type]
end
def commentable_id
comment_params[:commentable_id]
end
def comment_id
comment_params[:comment_id]
end
def body
comment_params[:body]
end
def make_child_comment
return "" if comment_id.blank?
parent_comment = Comment.find comment_id
#comment.move_to_child_of(parent_comment)
end
def build_comment(comment_params)
if current_user.nil?
user_id = nil
first_name = comment_params[:first_name]
last_name = comment_params[:last_name]
else
user_id = current_user.id
first_name = current_user.first_name
last_name = current_user.last_name
end
commentable = commentable_type.constantize.find(commentable_id)
Comment.build_from(commentable, user_id, comment_params[:body],
first_name, last_name)
end
end
comments/form: (this is on the Posts#show page)
<%= form_for #new_comment do |f| %>
<% if #new_comment.errors.any? %>
<div id="errors">
<h2><%= pluralize(#new_comment.errors.count, "error") %> encountered, please check your input.</h2>
<ul>
<% #new_comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% end %>
I would instead use nested routes to create a more restful and less tangled setup:
concerns :commentable do
resources :comments, only: [:create]
end
resources :posts, concerns: :commentable
This will give you a route POST /posts/1/comments to create a comment.
In your controller the first thing you want to do is figure out what the parent of the comment is:
class CommentsController < ApplicationController
before_action :set_commentable
private
def set_commentable
if params[:post_id]
#commentable = Post.find(params[:post_id])
end
end
end
This means that we no longer need to pass the commentable as form parameters. Its also eliminates this unsafe construct:
commentable = commentable_type.constantize.find(commentable_id)
Where a malicous user could potentially pass any class name as commentable_type and you would let them find it in the DB... Never trust user input to the point where you use it to execute any kind of code!
With that we can start building our create action:
class CommentsController < ApplicationController
before_action :set_commentable
def create
#comment = #commentable.comments.new(comment_params) do |comment|
if current_user
comment.user = current_user
comment.first_name = current_user.first_name
comment.last_name = current_user.last_name
end
end
if #comment.save
respond_to do |format|
format.json { head :created, location: #comment }
format.html { redirect_to #commentable, success: 'Comment created' }
end
else
respond_to do |format|
format.html { render :new }
format.json { render json: #comment.errors, status: 422 }
end
end
end
private
# ...
def comment_params
params.require(:comment).permit(:first_name, :last_name, :body, :humanizer_answer, :humanizer_question_id)
end
end
In Rails when the user submits a form you do not redirect the user back to the form - instead you re-render the form and send it as a response.
While you could have your CommentsController render the show view of whatever the commentable is it will be quite brittle and may not even provide a good user experience since the user will see the top of the post they where commenting. Instead we would render app/views/comments/new.html.erb which should just contain the form.
Also pay attention to how we are responding. You should generally avoid using redirect_to :back since it relies on the client sending the HTTP_REFERRER header with the request. Many clients do not send this!
Instead use redirect_to #commentable or whatever resource you are creating.
In your original code you have totally mixed up JSON and HTML responses.
When responding with JSON you do not redirect or send flash messages.
If a JSON POST request is successful you would either:
Respond with HTTP 201 - CREATED and a location header which contains the url to the newly created resource. This is preferred when using SPA's like Ember or Angular.
Respond with HTTP 200 - OK and the resource as JSON in the response body. This is often done in legacy API's.
If it fails do to validations you should respond with 422 - Unprocessable Entity - usually the errors are rendered as JSON in the response body as well.
Added.
You can scrap your Comment.build_from method as well which does you no good at all and is very idiosyncratic Ruby.
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
def show
#post = Post.friendly.find(params[:id])
#new_comment = #post.comments.new
end
end
Don't use line contiuation (\) syntax like that - use parens.
Don't:
new \
:commentable => obj,
:body => comment,
:user_id => user_id,
:first_name => first_name,
:last_name => last_name
Do:
new(
foo: a,
bar: b
)
Added 2
When using form_for with nested resources you pass it like this:
<%= form_for([commentable, comment]) do |f| %>
<% end %>
This will create the correct url for the action attribute and bind the form to the comment object. This uses locals to make it resuable so you would render the partial like so:
I'm assuming your form_for submits a POST request which triggers the HTML format in CommentsController#create:
def create
#comment = build_comment(comment_params)
respond_to do |format|
if #comment.save
make_child_comment
format.html
format.json { redirect_to(:back, :notice => 'Comment was successfully added.')}
else
format.html
format.json { redirect_to(:back, :flash => {:error => #comment.errors}) }
end
end
end
So, if #comment.save fails, and this is an HTML request, the #create method renders create.html. I think you want to render Posts#show instead.
Keep in mind that if validations fail on an object (Either by calling save/create, or validate/valid?), the #comment object will be populated with errors. In other words calling #comment.errors returns the relevant errors if validation fails. This is how your form is able to display the errors in #new_comment.errors.
For consistency, you'll need to rename #new_comment as #comment in the posts#show action, otherwise you'll get a NoMethodError on Nil::NilClass.
TL;DR: You're not rendering your form again with your failed #comment object if creation of that comment fails. Rename to #comment in posts, and render controller: :posts, action: :show if #comment.save fails from CommentsController#create
I have figured out the answer myself with the help of others here.
The reason is that I messed up with the JSON format and html format (typical noobie error)
To be able to display the errors using the code I need to change two places ( and change #comment to #new_comment as per #Anthony's advice).
1.
routes.rb:
resources :comments, defaults: { format: 'html' } # I set it as 'json' before
2.
CommentsController.rb:
def create
#new_comment = build_comment(comment_params)
respond_to do |format|
if #new_comment.save
make_child_comment
format.html { redirect_to(:back, :notice => 'Comment was successfully added.') }
else
commentable = commentable_type.constantize.find(commentable_id)
format.html { render template: 'posts/show', locals: {:#post => commentable} }
format.json { render json: #new_comment.errors }
end
end
end
My application has two associated models: Magazine and Article:
class Magazine < ActiveRecord::Base
has_one :article
end
class Article < ActiveRecord::Base
belongs_to :magazine
validation_presence_of :title
end
From the Magazine show page I can create a new Article, so my routes.rb is configured like:
resources :magazines, :shallow => true do
resources :articles
end
and in the Magazine show page I have the link "New article", like:
<%= link_to 'New article', new_magazine_article_path(#article)
and an article helper to pass correct parameters to the form_for:
module ArticlesHelper
def form_for_params
if action_name == 'edit'
[#article]
elsif action_name == 'new'
[#magazine, #article]
end
end
end
so I can use Article form_for like:
<%= simple_form_for(form_for_params) do |f| %> ...
The ArticlesController methods for new and create are:
respond_to :html, :xml, :js
def new
#magazine = Magazine.find(params[:magazine_id])
#article = Article.new
end
def create
#magazine = Magazine.find(params[:magazine_id])
#article = #magazine.build_article(params[:article])
if #article.save
respond_with #magazine # redirect to Magazine show page
else
flash[:notice] = "Warning! Correct the title field."
render :action => :new
end
end
The problem occurs when there is a validation error with the title attribute, and the action new is rendered. In this moment I get the message: undefined method `model_name' for NilClass:Class in the first line of form_for. I think it is because the #magazine parameter passed in the helper.
How could I solve this problem withou use redirect_to ? (I want to mantain the other attributes that were filled in the form .)
Your form_for_params method is returning nil, because the action_name is set to 'create', not 'new' or 'edit'.
Try this:
elseif action_name == 'new' or action_name == 'create'
[#magazine, #article]
I have the following relationship:
store.rb -> has_many :products
product.rb -> belongs_to :store
routes.rb
resources :stores do
resources :products
end
builds_controller.rb
def edit
#build = Build.find(params[:id])
#user = User.find(#build.user_id)
#hero = Hero.find(#build.hero_id)
#heros = Hero.order('name ASC')
#items = Item.order('name ASC')
unless current_user.id == #user.id
respond_to do |format|
format.html { redirect_to root_path, notice: 'You are only allowed to edit your own builds' }
end
end
end
For some reason, whenever I try go to the edit page for a build and try to edit it, it runs the create action instead of update.
Anyone know what might be the cause of this?
Also, I'd like the form on the edit page to be filled out with the current data of the build. How do I achieve this?
My repo: https://github.com/imjp/DotA-Items
Your problem comes from your form:
<%= semantic_form_for([current_user, current_user.builds.build]) do |f| %>
It needs to be
<%= semantic_form_for([#user, #build]) do |f| %>
Now, in your new action, you also need to prepare the required variables:
#user = User.find(params[:user_id]) #assuming you have a path like users/id/builds/new
# or #user = current_user if that's what you want
#build = #user.builds.build
ps: you should use the cancan gem to manage authorizations, instead of doing stuff like if #user.id == current_user.id etc.