issue with nested route in Rails 5.1 application - ruby-on-rails

I'm building an application for students to register an absence during a exam.
my routes look like this:
resources :users do
resources :absences
end
the form to create an absence looks like this:
<%= form_for [#user, #absence] do |f| %>
<%= f.label :course_id, "Vak" %>
<%= f.select :course_id, options_for_courses, label: "Vak" %>
<%= f.label :date, "Datum" %>
<%= f.text_field :date %>
<%= f.submit "Afwezigheid aanvragen" %>
<% end %>
the Absence model looks like this:
class Absence < ApplicationRecord
...
belongs_to :user
end
the User model looks like this:
class User < ApplicationRecord
...
has_many :absences
end
The AbsencesController looks like this:
class AbsencesController < ApplicationController
...
def new
#user = current_user
#absence = current_user.absences.build
end
def create
#absence = Absence.create(absence_params)
if #absence.save
redirect_to root_path, notice: "Afwezigheid succesvol aangevraagd"
else
flash[:alert] = "Er was een probleem met het registreren van de afwezigheid"
render :new
end
end
But when I try to register an absence, I get an error in the form:
The error log looks like this:
Started POST "/users/2/absences" for 127.0.0.1 at 2017-09-21 09:51:55 +0200
Processing by AbsencesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"esW1HeKf+VG8+RC4Zm2F7d8BFewddk6aIssj0Y90s+UC4oTUxpO1iw9hR9M94Jfov5bjeB0aQPRHG0qvENO3Tg==", "absence"=>{"course_id"=>"2", "date"=>"27/09/2017"}, "commit"=>"Afwezigheid aanvragen", "user_id
"=>"2"}
(0.1ms) BEGIN
Course Load (0.3ms) SELECT "courses".* FROM "courses" WHERE "courses"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
(0.1ms) ROLLBACK
(0.1ms) BEGIN
(0.1ms) ROLLBACK
Rendering absences/new.html.erb within layouts/application
Rendered absences/new.html.erb within layouts/application (145.6ms)
Completed 500 Internal Server Error in 616ms (ActiveRecord: 0.9ms)
ActionView::Template::Error - undefined method `absences_path' for #<#<Class:0x007fb911af0420>:0x007fb90baab5d8>
Did you mean? asset_path:
actionpack (5.1.3) lib/action_dispatch/routing/polymorphic_routes.rb:230:in `polymorphic_method'
actionpack (5.1.3) lib/action_dispatch/routing/polymorphic_routes.rb:138:in `polymorphic_path'
actionview (5.1.3) lib/action_view/helpers/form_helper.rb:472:in `apply_form_for_options!'
actionview (5.1.3) lib/action_view/helpers/form_helper.rb:440:in `form_for'
I'm not sure what's going on, have I missed something?
thanks for your help,
Anthony

There is an error while creating your object and you are doing render :new which is rendering the form again but with #user as nil. So what happens that <%= form_for [nil, #absence] do |f| %> makes the path as absences_path which is not present. So either fetch #user in create action too or just use current_user everywhere. Like this:
def new
#user = current_user # You may remove this
#absence = current_user.absences.build
end
def create
#absence = Absence.create(absence_params)
if #absence.save
redirect_to root_path, notice: "Afwezigheid succesvol aangevraagd"
else
#user = current_user # If you don't want to remove this line from new action then add it here too
flash[:alert] = "Er was een probleem met het registreren van de afwezigheid"
render :new
end
end
And in the form if you remove the #user from controller then do it like this:
<%= form_for [current_user, #absence] do |f| %>
Update:
To save the object in create action change this line:
#absence = Absence.create(absence_params)
to
#absence = current_user.absences.build(absence_params)
Your user_id was nil because there was no input box or any hidden field for that and that is correct. You just need to build the object again against the current_user. And I have changed the create to build because create directly saves the object to database instead you want to build it first and then you are trying to save it.
Hope this helps.

You are getting problem in your create method when you are rendering the new because form doesn't getting the #user
add following in your create method & check again
#user = current_user

Okay, you just add return after this line:
redirect_to root_path, notice: "Afwezigheid succesvol aangevraagd"
return
As your log says, you are trying to render :new template at the end of the method, you need to add return to stop it after redirect_to
or delete that line at the end of your create method
render :new
Also change this:
#absence = current_user.absences.build(absence_params)

Related

How to submit records in two models at the same time in Rails?

I'm trying to submit 2 model via one form.
Despite I created these codes, the form creates only "Article" record.
"ArticleHistory" record should be created in this form. But at the moment, the form won't.
Does anyone know what's wrong with my code? How come?
Here's my view(slim).
h2 It's new article
p test
= form_for #article do |f|
= f.fields_for :article_history do |af|
.field
= af.label :title
br
= af.text_field :title
.field
= af.label :content
br
= af.text_area :content
= af.hidden_field :id
.field
= f.hidden_field :status, :value => "publish"
= f.hidden_field :user_id, :value => current_user.id
= f.hidden_field :current_version, :value => 1
.actions
= f.submit 'submit'
Also my model.
class Article < ApplicationRecord
has_many :article_histories
accepts_nested_attributes_for :article_histories
end
class ArticleHistory < ApplicationRecord
belongs_to :article
end
And here's controller. I quoted only necessary part.
class ArticlesController < ApplicationController
def new
#article = Article.new
#article.article_histories.build
end
def create
#article = Article.new(article_params)
if #article.status == "publish"
#article.publish_datetime = Time.now.to_s(:db)
end
if params[:article][:back]
render :new
elsif #article.save
redirect_to #article, notice: 'Article was successfully created.'
else
render :new
end
end
private
def article_params
params.require(:article).permit(:current_version , :status , :user_id , article_histories_attributes:[:id, :article_id, :version, :title, :content])
end
end
BTW these are version information.
$ bin/rails -v
Rails 5.0.1
$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin14]
Thanks in advance!
[ActiveRecord Error]
Started POST "/articles" for ::1 at 2017-01-13 12:10:35 +0900
Processing by ArticlesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ZUMBqozYl4vWNxImgz8ghY7gGb2NPp4qMnXYulqkoPAMTWueIN8xAITCo7NSDP/rDgJQOLNOEqLwUcy3kl4BTQ==", "article"=>{"article_history"=>{"title"=>"title1210", "content"=>"content1210", "id"=>""}, "status"=>"publish", "user_id"=>"9", "current_version"=>"1"}, "commit"=>"確認"}
User Load (0.8ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 9 ORDER BY `users`.`id` ASC LIMIT 1
Unpermitted parameter: article_history
(0.6ms) BEGIN
SQL (0.7ms) INSERT INTO `articles` (`current_version`, `status`, `publish_datetime`, `user_id`, `created_at`, `updated_at`) VALUES (1, 'publish', '2017-01-13 03:10:43', 9, '2017-01-13 03:11:05', '2017-01-13 03:11:05')
(2.3ms) COMMIT
inside your elsif #article.save you can call
ArticleHistory.create(article_params)
or since they are associated then you can simple call (im not completely sure about "article_histories" you can try in you console)
#article.article_histories.create(article_params)
then you can change the article_params to the parameters needed.
Sorry, It's my mistake. I noticed my error.
My form is originally like this.
h2 It's new article
p test
= form_for #article do |f|
= f.fields_for :article_history do |af|
Changed into this
h2 It's new article
p test
= form_for #article do |f|
= f.fields_for :article_histories do |af|
Finally, it works. Thank you all contributions!

One controller uses its corresponding layout, but the other does not

My Rails project has two controllers, a SessionsController and a CustomersController. My project also has two layout views in the layouts directory, sessions.html.erb and customers.html.erb.
I would like the SessionsController to render using the session.html.erb layout and the CustomersController to render using the customers.html.erb layout.
I have removed all explicit layout commands from my project, so as far as I understand it the required functionality should now occur.
However, what is actually happening is that the CustomersController is using customers.html.erb layout as expected but the SessionsController is using no layout at all. (I removed the default application.html.erb layout entirely, so it has nothing to fall back on.)
I have ALSO tried explicitly setting the layouts in each controller with layout: ".html.erb", but the behaviour was the same.
Any ideas what may be causing this?
SessionsController:
class SessionsController < ApplicationController
extend ActiveModel::Naming
def initialize
#errors = ActiveModel::Errors.new(self)
end
attr_accessor :name
attr_reader :errors
def read_attribute_for_validation(attr)
send(attr)
end
def SessionsController.human_attribute_name(attr, options = {} )
attr
end
def SessionsController.lookup_ancestors
[self]
end
def new
end
def create
customer = Customer.find_by(customerID: params[:session][:user_id])
if customer && customer.correct_password?(params[:session][:password])
log_in customer
remember customer
customer.update_last_action_time
redirect_to customer
else
#errors.add(:base, "UserID cannot be blank") if params[:session][:user_id].blank?
#errors.add(:base, "Password cannot be blank") if params[:session][:password].blank?
# Only push this final messag if there were no other errors
#errors.add(:base, "Unrecognised UserID / password combination") if #errors.count == 0
render "new"
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
CustomersController:
class CustomersController < ApplicationController
before_action :logged_in_customer
before_action :correct_customer
def seo
# #customer = Customer.find_by(params[:customerID])
end
def show
# #customer = Customer.find_by(params[:customerID])
end
def edit
# #customer = Customer.find_by(params[:customerID])
end
def update
# #customer = Customer.find_by(params[:customerID])
if #customer.update(customer_params)
render "edit"
else
render "edit"
end
end
private
def customer_params
params.require(:customer).permit( :new_email,
:new_password,
:new_password_confirmation,
:existing_password,
:update_type )
end
def logged_in_customer
if logged_in? && !timed_out?
# Intentionally blank
else
redirect_to login_url
end
end
def correct_customer
#customer = Customer.find(params[:id])
redirect_to(customer_path(current_customer)) unless current_customer?(#customer)
end
end
sessions.html.erb:
<%= render "shared/intro" %>
<div class="header">
<%= image_tag "rebrand.png", alt: "株式会社リブランド" %>
</div>
<div class="main-panel">
<%= yield %>
</div>
<div class="footer">
SEO-care
</div>
<%= render "shared/outro" %>
customers.html.erb:
<%= render "shared/intro" %>
<%= render "shared/header" %>
<%= render "shared/left_panel" %>
<%= render "shared/top_panel" %>
<!-- add any always-used formatting here -->
<div class="main-panel">
<%= yield %>
<!-- add any always-used formatting here -->
</div>
</div>
<%= render "shared/footer" %>
<%= render "shared/outro" %>
I have not included the content of the partials, but think I can rule them out as the application seems to not even find the layout file, let alone get around to considering the contents of its partials. This can be seen in the console output when rendering a page from each controller:
Sessions:
Rendered sessions/new.html.erb (12.0ms)
Completed 200 OK in 30ms (Views: 20.0ms | ActiveRecord: 0.0ms)
Customers:
Rendered customers/show.html.erb within layouts/customers (0.0ms)
Rendered shared/_intro.html.erb (183.1ms)
Rendered shared/_header.html.erb (5.0ms)
Rendered shared/_left_panel.html.erb (1.0ms)
Rendered shared/_top_panel.html.erb (0.0ms)
Rendered shared/_footer.html.erb (1.0ms)
Rendered shared/_outro.html.erb (1.0ms)
Completed 200 OK in 249ms (Views: 203.2ms | ActiveRecord: 39.4ms)
Quick solution:
The initialize method appears to be overriding one in a base class. A quick solution is to ensure the base initialize method is also called using super:
def initialize
super
#errors = ActiveModel::Errors.new(self)
end
Potentially better solution:
Extending ActiveModel::Naming to expose the error message functionality may not be the best way of handling errors in this case; finding a way to add the errors to a customer object and then using those errors (thus negating the need to extend ActiveModel::Naming at all) may be a preferred long-term solution. Thanks to #maxcal for this suggestion.

Authenticate model issue

I have a rails 4 app where I want to have certain books require a secret password to see.
I have a model called Book.rb:
class Book < ActiveRecord::Base
has_secure_password :validations => false
end
and a sign_in.html.haml that has the form:
= form_for login_book_path do |f|
= f.password_field :password
= f.submit "View Book"
and the login book path routes to the following controller method:
def book_login
#book = Book.find_by(slug: params[:slug])
respond_to do |format|
if #book.authenticate(params[:password])
format.html { redirect_to root_path }
else
format.html { render :sign_in }
end
end
end
If I hardcode the password (like #book.authenticate("thesecretpassword"), it correctly redirects me to the root_url. But for some reason, without hardcoding it doesn't work (just sends me back to sign_in.html.haml?
Here's the output in the terminal when I hit the "VIEW BOOK" button:
Started POST "/the-wizard-book/sign_in" for 127.0.0.1 at 2015-02-24 14:04:48 -0700
Processing by BooksController#book_login as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Nx2OkI/FjJwq6zmcT5KLSkQpr9hYa83RL7CFmLQW0/g=", "/the-wizard-book/sign_in"=>{"password"=>"[FILTERED]"}, "commit"=>"View Book", "slug"=>"the-wizard-book"}
Load (0.2ms) SELECT "books".* FROM "books" WHERE "books"."slug" = 'the-wizard-book' LIMIT 1

One to Many Association Object Will Not Save

I set up a has_many belongs_to relationship in my two models and followed Ryan Bates' screencast on how to set up the controller. When I submit my form to create the new object, the nested object does not save for some reason. Here are my models:
class Auction < ActiveRecord::Base
has_many :bids, dependent: :destroy
end
class Bid < ActiveRecord::Base
belongs_to :auction
belongs_to :user
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :auction_id, presence: true
end
and my nested object controller:
class BidsController < ApplicationController
def index
#auction = Auction.find(params[:auction_id])
#bids = #auction.bids
end
def new
#auction = Auction.find(params[:auction_id])
#bid = #auction.bids.build
end
def create
#auction = Auction.find(params[:auction_id])
#bid = #auction.bids.create(params[:bid])
#bid.save
if #bid.save
flash[:success] = "Bid has been successfully placed."
else
#bid.errors
render 'new'
end
end
def destroy
#auction = Auction.find(params[:auction_id])
#bid = #auction.bids.find
#bid.destroy
flash[:notice] = "Successfully destroyed Bid."
redirect_to auction_url(#bid.article_id)
end
end
my form:
<h1>Create a New Bid</h1>
<%= form_for ([#auction, #bid]) do |f|%>
<p>
<%= f.submit %>
</p>
<%end%>
and my terminal output:
Started POST "/auctions/1/bids" for 127.0.0.1 at 2014-11-30 17:59:13 -0600
Processing by BidsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"dkZBcab1rgZjtJGF3LAJ//exK6liglZ0Fy4mg7HWEt0=", "commit"=>"Create Bid", "auction_id"=>"1"}
Auction Load (0.1ms) SELECT "auctions".* FROM "auctions" WHERE "auctions"."id" = ? LIMIT 1 [["id", 1]]
(0.0ms) begin transaction
(0.0ms) commit transaction
(0.0ms) begin transaction
(0.0ms) rollback transaction
(0.0ms) begin transaction
(0.0ms) rollback transaction
Thanks for your help.
Your bid object needs a user_id because you have validates :user_id, presence: true in the class definition.
When you call #bid.save in the controller, however, #bid does not have a user_id value, therefore the transaction gets rolled back because of the failing validation.
You should be able to see this by looking at #bid.errors.full_messages in the create action, after you've called #bid.save. (Look up the pry gem if you're not already familiar with it...it would be a perfect tool to let you do this inspection.)
Try replacing your create action with this:
def create
#auction = Auction.find(params[:auction_id])
#bid = #auction.bids.new params[:bid].merge(user_id: current_user.id)
if #bid.save
flash[:success] = "Bid has been successfully placed."
else
flash[:error] = #bid.errors.full_messages.join('. ')
render 'new'
end
end
This assumes that you have access to the current user in the controller as current_user. Devise and other popular auth solutions supply this, or you can do so yourself.
Note also that your original code tries to write #bid to the database 3 separate times, which is twice more than you need to. Here are the offending lines:
def create
...
#bid = #auction.bids.create(params[:bid])
#bid.save
if #bid.save
...
#create instantiates an object and attempts to write it to the database. In my code above, I've replaced #auction.bids.create(params...) with #auction.bids.new(params...). This initializes #bid without trying to persist it to the db.
I also removed the first #bid.save because the line below it if #bid.save will accomplish the same thing.
Finally, your line #bid.errors doesn't do anything useful. I modified it to store the error messages in your flash hash, which you can then use in your view to display the errors to the user.

Edit inserting new record

I have a Rails 4 Application where I have the following code:
my _form_html.erb
<%= nested_form_for #store, :html => {:multipart => true, :honeypot => true} do |f| %>
<%= f.text_field :name %>
<% if params[:action] == "new" %>
<textarea name="store[products_attributes][0][product_fields_attributes][0][text_content]"></textarea>
<% else %>
<textarea name="store[products_attributes][0][product_fields_attributes][0][text_content]">VALUE</textarea>
<% end %>
<%= f.submit%>
<% end %>
My controller looks like:
before_action :set_store, only: [:show, :edit, :update, :destroy]
def new
#store = Store.new
end
def edit
end
def create
#store = Store.new(store_params)
respond_to do |format|
if #store.save
format.html { redirect_to #store, notice: 'Store was successfully created.'}
format.json { render action: 'show', status: :created, location: #store }
else
format.html { render action: 'new' }
format.json { render json: #store.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #store.update(store_params)
format.html { redirect_to #store, notice: 'Store was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #store.errors, status: :unprocessable_entity }
end
end
def set_store
#store = Store.find(params[:id])
end
def store_params
params.require(:store).permit(:name, products_attributes: [:id, { product_fields_attributes: [:id, :text_content] } ])
end
Also my edit.html.erb looks like:
<h3>Edit</h1>
<%= render 'form' %>
and my new.html.erb looks like:
<h3>Add New</h1>
<%= render 'form' %>
and in my rails console when I click "Update" looks like:
Started PATCH "/stores/sNx92thyjcP_jw" for 127.0.0.1 at 2014-05-27 17:10:46 -0600
Processing by StoresController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"nFUg4ynXYyg99rPPPoa3uO/iHP4LT1XlOz3Vm3Zm4Z0=", "store"=>{"name"=>"Testing", "description"=>"", "products_attributes"=>{"0"=>{"type_of"=>"Book", "product_fields_attributes"=>{"0"=>{"text_content"=>"testing testing testing 1"}}}}}, "commit"=>"Update Store", "token"=>"sNx92thyjcP_jw"}
Site Load (0.7ms) SELECT "stores".* FROM "stores" WHERE "stores"."token" = 'sNx92thyjcP_jw' LIMIT 1
(0.2ms) BEGIN
SQL (0.5ms) INSERT INTO "products" ("created_at", "store_id", "type_of", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Tue, 27 May 2014 23:10:46 UTC +00:00], ["store_id", 102], ["type_of", "Book"], ["updated_at", Tue, 27 May 2014 23:10:46 UTC +00:00]]
SQL (0.7ms) INSERT INTO "product_fields" ("created_at", "text_content", "updated_at", "product_id") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Tue, 27 May 2014 23:10:46 UTC +00:00], ["text_content", "testing testing testing 1"], ["updated_at", Tue, 27 May 2014 23:10:46 UTC +00:00], ["product_id", 111]]
(15.5ms) COMMIT
Redirected to http://localhost:3000/products/sNx92thyjcP_jw
Completed 302 Found in 30ms (ActiveRecord: 17.6ms)
My store model:
class Store < ActiveRecord::Base
before_create :generate_token
has_many :products
accepts_nested_attributes_for :products
def to_param
token
end
private
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64(10, false)
break random_token unless Store.exists?(token: random_token)
end
end
My product model:
class Product < ActiveRecord::Base
belongs_to :store
has_many :product_fields
accepts_nested_attributes_for :product_fields
end
My product fields model:
class ProductField < ActiveRecord::Base
belongs_to :product
mount_uploader :image_content, ImageUploader
end
But when you go to edit the store, instead of updating, it adds a new record. For example, on the new page, you put in the textarea "Testing 1", and then save. Then you go to the edit page and edit the textarea that says "Testing 1" to be "Testing 2", and click save. Now I have two records: "Testing 1" and "Testing 2".
What is going on here? Thanks for all help!
Ok, for some reason you are using the nested_form_for helper, but you are not using nested fields at all, instead you write your html for the nested textarea manually, with a fixed id [0]? This is why it always creates a new nested field. When saving the store, it will check if the given ids exist, and if not (e.g. id 0 never exists), it will create a new record for it.
Using nested fields in rails is actually pretty simple, you should just write
<%= form_for #store, :html => {:multipart => true, :honeypot => true} do |f| %>
<%= f.text_field :name %>
<%= f.fields_for :products do |product| %>
<%= product.text_area :text_content %>
<% end %>
<%= f.submit%>
<% end %>
You are currently not using any dynamic adding (afaik), so you do not need to use the nested_form_for. From the exmaple, I am assuming you always want just one product?
In your controller you will have to change your new action to also create the initial product to make this work.
def new
#store = Store.new
#store.products.build
end
This will add one empty/new product, which you can then fill in.
You are using nested attributs in the model so when editing the name you're creating a new associated model. The ID of the nested model should not be editable.
Check this documentation:
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
It's creating a new instance with new ID.
:update_only
For a one-to-one association, this option allows you to
specify how nested attributes are to be used when an associated record
already exists. In general, an existing record may either be updated
with the new set of attribute values or be replaced by a wholly new
record containing those values. By default the :update_only option is
false and the nested attributes are used to update the existing record
only if they include the record's :id value. Otherwise a new record
will be instantiated and used to replace the existing one. However if
the :update_only option is true, the nested attributes are used to
update the record's attributes always, regardless of whether the :id
is present. The option is ignored for collection associations.
I assumed that my _form_html.erb is actually _form.html.erb partial which you're calling from new and edit view.
Surely your form code is sending request to create action otherwise looking at your code there is no reason it will create new record. You should recheck it. Btw, I dont see any reason to use nested_form_for you can also use form_for field.
Anyway, when you visit the edit action ie /stores/12/edit like path, it should pre-populate all field. check it, may be made mistake in defining routes. Or, you might be sending ajax request in a wrong way. Possibility exist.
One more thing, there is no reason to use if-else condition, since the outcome of both condition seems similar.
<%= nested_form_for #store, :html => {:multipart => true, :honeypot => true} do |f| %>
<%= f.text_field :name %>
<textarea name="store[products_attributes][0][product_fields_attributes][0][text_content]"></textarea>
<%= f.submit%>
<% end %>
Params are not correctly formated, they should have this format:
params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
I'ts important to send the id in order to allow rails to search for the associated model. You are sending the "0" as a key instead of value.
Follow this guide to create the form:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
And the documentation about the nested params:
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Also an alternative:
http://matthewrobertson.org/blog/2012/09/20/decoupling-rails-forms-from-the-database/

Resources