Action Controller exception undefined method `merge' for xxx:String - ruby-on-rails

I'm doing simple Reddit like site. I'm trying to add button to reporting posts. I create report model, using button_to i try to post data to report controller to create it but i received NoMethodError in ReportsController#create undefined method merge' for "post_id":String
model/report.rb
class Report < ApplicationRecord
belongs_to :reporting_user, class_name: 'Author'
has_one :post
end
report_controller.rb
class ReportsController < ApplicationController
def create
report = Report.new(report_params)
flash[:notice] = if report.save
'Raported'
else
report.errors.full_messages.join('. ')
end
end
def report_params
params.require(:post).merge(reporting_user: current_author.id)
end
end
and button in view
= button_to "Report", reports_path, method: :post, params: {post: post}
What cause that problem?
edit:
params
=> <ActionController::Parameters {"authenticity_token"=>"sX0DQfM0rp97q8i16LGZfXPoSJNx15Hk4mmP35uFVh52bzVa30ei/Bxk/Bm40gnFmd2NvFEqj+Wze8ted66kig==", "post"=>"1", "controller"=>"reports", "action"=>"create"} permitted: false>

To start with you want to use belongs_to and not has_one.
class Report < ApplicationRecord
belongs_to :reporting_user, class_name: 'Author'
belongs_to :post
end
This correctly places the post_id foreign key column on reports. Using has_one places the fk column on posts which won't work.
And a generally superior solution would be to make reports a nested resource:
# /config/routes.rb
resources :posts do
resources :reports, only: [:create]
end
# app/controller/reports_controller.rb
class ReportsController
before_action :set_post
# POST /posts/:post_id/reports
def create
#report = #post.reports.new(reporting_user: current_author)
if #report.save
flash[:notice] = 'Reported'
else
flash[:notice] = report.errors.full_messages.join('. ')
end
redirect_to #post
end
private
def set_post
#post = Post.find(params[:post_id])
end
end
This lets you simplify the button to just:
= button_to "Report", post_reports_path(post), method: :post
Since the post_id is part of the path we don't need to send any additional params.
If you do want to let the user pass additional info through a form in the future a better way to create/update resources with params and session data is by passing a block:
#report = #post.reports.new(report_params) do |r|
r.reporting_user = current_user
end

ActionController::Parameters#require returns the value of the required key in the params. Usually this would be an object passed back from a form. In this example require would return {name: "Francesco", age: 22, role: "admin"} and merge would work.
Your view is sending back parameters that Rails is formatting into {post: 'string'}. We would need to see your view code to determine what exactly needs to change.
Update: From the new code posted we can see that the parameter sent back is "post"=>"1". Normally we would be expecting post: {id: 1, ...}.
Update: The button in the view would need the params key updated to something ala params: {post: {id: post.id}} EDIT: I agree that params: {report: { post_id: post}} is a better format.

The problem seems to be in report_params. When you call params.require(:post), it fetches :post from params -> the result is string. And you are calling merge on this string.
I'd recommend change in view:
= button_to "Report", reports_path, method: :post, params: { report: { post_id: post} }
then in controller:
def report_params
params.require(:report).permit(:post_id).merge(reporting_user_id: current_author.id)
end
Note, that I changed also the naming according to conventions: model_id for id of the model, model or model itself.

Related

Following Rails tutorial, undefined method 'create' for nil:NilClass, with nested resource

I'm following the Rails tutorial and making changes where appropriate, with the intention that my tutorial project will become a full-fledged production app after the completion of the tutorial.
I've run into a snag with the second model portion of the tutorial. Here is how I've written my second model.
In my policy.rb:
class Policy < ApplicationRecord
has_one :insured
end
In my insured.rb:
class Insured < ApplicationRecord
belongs_to :policy
end
In my routes.rb:
resources :policies do
resource :insured
end
In my insureds_controller.rb:
def create
#policy = Policy.find(params[:policy_id])
# next line is raising the error
#insured = #policy.insured.create(insured_params)
redirect_to #insured
end
private
def insured_params
params.permit(:name, :address, :phone, :email)
end
I've inspected the #policy object with render plain: #policy.inspect and can confirm that ActiveRecord is retrieving the policy correctly. When I inspect the attributes of #policy, using render plain: #policy.attribute_names.inspect, I don't see an insured attribute, which I thought Rails was supposed to automatically manage for me. In the tutorial, an article has_many :comments, and a comment is supposedly easily created and associated with the parent article with this call: #article.comments.create(comment_params). I also noticed that the tutorial uses params.require(:comment).permit(...) while I have to use params.permit(...), after inspecting the params hash I saw that the :insured attributes existed in the top-level of the hash, instead of being tied to an :insured key within the hash.
I tried manually saving and assigning the #insured object like so:
def create
#policy = Policy.find(params[:policy_id])
#insured = Insured.new(insured_params)
if #insured.save
#policy.insured = #insured
redirect_to #insured
end
end
Only to run into the following error in my .../insureds/new.html.erb:
<h1>New Insured</h1>
<h1><%= #policy.policy_number %></h2>
<%= render 'form' %>
<%= link_to 'Cancel', policy_path(#policy) %>
Which derives from my partial form .../insureds/_form.html.erb:
# the following line raises the error
<%= form_with model: #insured, local: true do |form| %>
# html omitted for brevity
<% end %>
Error: 'undefined method insureds_path'. This is weird because when I inspect the HTML I can see the form action for this view is /policies/[:id]/insured.
Sorry for the massive wall of text, I wanted to show you guys that I did try to figure out what is going wrong.
There is an error in your config/routes.rb file:
resources :policies do
# change it for:
collection do
get 'insured', to: 'policies#show_insured', as: 'show_policy_insured'
# maybe unnecessary to be here
# get 'insured/new', to: 'insureds#new', as: 'new_policy_insured'
# post 'insured/create', to: 'insureds#create', as: 'create_policy_insured'
# delete 'insured/delete', to: 'insureds#delete', as: 'delete_policy_insured'
end
end
# add resources here
resources :insureds
In policy_controller.rb:
def show_insured # 'policy/:id/insureds/
end
In insureds_controller.rb:
def show # '/insureds/:id'
end
def create
...
redirect_to show_policy_insured && return if #insured_policy
end
# before_filter or before_action
#policy = Policy.find(params[:id])
#insured_policy = #policy.insured
Check it and run this to see your routes:
$ bundle exec rake routes
get /policies/:id/insured => 'policies_controller#show_insured'
get /insureds/:id => 'insureds_controller#show'
get /insured/new => 'insureds_controller#new'
post /insureds/create => 'insureds_controller#create'
delete /insureds/:id/delete => 'insureds_controller#delete'
#maguri, that's not all necessary. The stumbling block I was running into was that Rails couldn't automatically determine the correct routes. When I provided my own urls in the form_with declarations, everything went smoothly.
Observe the following change in my _form.html.erb for the Insured model, which belongs_to Policy, which has_one Insured.
<%= form_with model: #insured, url: policy_insured_path(#policy) local: true do |form| %>
In my updated insureds_controller.rb file, using #Phlip's suggestion:
def create
#policy = Policy.find(params[:policy_id])
#insured = #policy.create_insured(insured_params)
if #policy.insured.save
redirect_to policy_insured_path(params[:policy_id])
else
render 'new'
end
end
This allows me to keep routes.rb clean and simple:
resources :policies do
resource: insured
end
Thank you for your answer, it helped me discover the problem was with my routes.

How do I pass objects from one controller to another in rails?

How do you pass an object built and submitted from one controller's show action to the create action of another controller while also retaining the instance variable of the former?
ItemsController:
def show
#item = Item.friendly.find(params[:id])
#trade = current_user.requested_trades.build
end
The form_for on my show page then makes a post request for #trade, with :wanted_item and :trade_requester as params.
TradesController:
def create
#item = ???
#trade = current_user.requested_trades.build
if #trade.save
format.html { redirect_to #item, notice: "success" }
else
format.html { redirect_to #item, notice "pick another number" }
end
etc...
end
Trade.rb:
belongs_to :trade_requester, class_name: "User"
belongs_to :trade_recipient, class_name: "User"
belongs_to :wanted_item, class_name: "Item"
belongs_to :collateral_item, class_name: "Item"
Routes.rb
resources :trades do
member do
post :accept
post :reject
end
end
Something about this feels wrong? Other questions on this subject seem to be about passing an object between different actions within the same controller - what I'm asking is not that.
First, I think I would make my routes more like:
resources :wanted_items do
resources :trades, shallow: true do
member do
post :accept
post :reject
end
end
end
Which would give you:
accept_trade POST /trades/:id/accept(.:format) trades#accept
reject_trade POST /trades/:id/reject(.:format) trades#reject
wanted_item_trades GET /wanted_items/:wanted_item_id/trades(.:format) trades#index
POST /wanted_items/:wanted_item_id/trades(.:format) trades#create
new_wanted_item_trade GET /wanted_items/:wanted_item_id/trades/new(.:format) trades#new
edit_trade GET /trades/:id/edit(.:format) trades#edit
trade GET /trades/:id(.:format) trades#show
PATCH /trades/:id(.:format) trades#update
PUT /trades/:id(.:format) trades#update
DELETE /trades/:id(.:format) trades#destroy
wanted_items GET /wanted_items(.:format) wanted_items#index
POST /wanted_items(.:format) wanted_items#create
new_wanted_item GET /wanted_items/new(.:format) wanted_items#new
edit_wanted_item GET /wanted_items/:id/edit(.:format) wanted_items#edit
wanted_item GET /wanted_items/:id(.:format) wanted_items#show
PATCH /wanted_items/:id(.:format) wanted_items#update
PUT /wanted_items/:id(.:format) wanted_items#update
DELETE /wanted_items/:id(.:format) wanted_items#destroy
Then in your form_for, I would do something like:
<% form_for wanted_item_trades_path(wanted_item: #wanted_item, trade: #trade) do |f| %>
...
<% end %>
That form_for syntax may not be exactly right, so you may need to futz with it.
This will generate a url something like:
/wanted_items/3/trades
Naturally, the '3' is just made up. It'll be whatever your #item.id is.
When you post the form, you should have a wanted_item_id in your params of 3. Then, in your TradesController, you'll do something like:
class TradesController < ApplicationController
def create
#wanted_item = Item.find_by(id: params[:wanted_item_id])
#trade = current_user.requested_trades.build(wanted_item: #wanted_item)
if #trade.save
format.html { redirect_to #item, notice: "success" }
else
format.html { redirect_to #item, notice "pick another number" }
end
end
...
end
BTW, it looks like you're using friendly_id. So, you could tweak all of the above to use a friendly_id instead of id. I don't use friendly_id, so you'll have to sort that on your own.

Ruby on Rails - Pass ID from another controller

I have two models. First is Taxirecord and second is Carpark. Each Taxirecord may have its own Carpark. I have a problem with passing taxirecord_id to Carpark record. I have route
car_new GET /taxidetail/:taxirecord_id/carpark/new(.:format) carparks#new
And i want to pass :taxirecord_id, which is id of taxirecord that im editing, to my create controller.
My carpark model:
class Carpark < ActiveRecord::Base
belongs_to :taxirecord
end
In controller im finding taxirecord_id by find function based on param :taxirecord_id, but id is nil when create is called. Can you please help me to find out what Im doing wrong and how Can I solve this problem? Thanks for any help!
My carpark controller
class CarparksController < ApplicationController
def new
#car = Carpark.new
end
def create
#car = Carpark.new(carpark_params, taxirecord_id: Taxirecord.find(params[:taxirecord_id]))
if #car.save
flash[:notice] = "Zaznam byl ulozen"
redirect_to root_path
else
flash[:notice] = "Zaznam nebyl ulozen"
render 'new'
end
end
private def carpark_params
params.require(:carpark).permit(:car_brand, :car_type, :driver_name, :driver_tel)
end
end
I finally get it work
Ive added <%=link_to 'New Carpark', {:controller => "carparks", :action => "new", :taxirecord_id => #taxi_record.id }%>
to my taxirecord form and to carpark form <%= hidden_field_tag :taxirecord_id, params[:taxirecord_id] %>
And to my carpark controller : #carpark.taxirecord_id = params[:taxirecord_id]
Thanks everyone for great support and help!
I'd lean towards using something like:
before_action :assign_taxirecord
...
private
def assign_taxirecord
#taxirecord = TaxiRecord.find(params[:taxirecord_id])
end
And then in the create action:
def create
#car = #taxirecord.build_carpark(carpark_params)
...
end
Obviously there's a little tailoring needed to your requirements (i.e. for what actions the before_action is called), but I hope that helps!
No need to send taxirecord id.
class Carpark < ApplicationRecord
belongs_to :taxirecord
end
class Taxirecord < ApplicationRecord
has_one :carpark
end
Rails.application.routes.draw do
resources :taxirecords do
resources :carparks
end
end
for new taxirecord
t = Taxirecord.new(:registration => "efgh", :description =>"test")
for new carpark
t.create_carpark(:description=>"abcd")
#=> #<Carpark id: 2, taxirecord_id: 2, description: "abcd", created_at: "2017-10-12 10:55:38", updated_at: "2017-10-12 10:55:38">

Can't test a controller when passing a hidden field

I have two objects, Course and TeacherRight. I create TeacherRight with a from placed on the Course 'show' view. I pass course_id to the TeacherRight controller as a hidden field:
=form_for(#right) do |f|
=hidden_field_tag :course_id, #course.id
%ul
%li=f.collection_select :user_id, User.order(:name)-#course.teachers, :id, :name, include_blank: false
%li=f.submit
The controller:
class TeacherRightsController < ApplicationController
def create
course = Course.find(params[:course_id])
#teacher_right = course.teacher_rights.build(teacher_right_params)
#teacher_right.save
redirect_to course
end
private
def teacher_right_params
params.require(:teacher_right).permit(:user_id)
end
#only owner can add teachers to course
def can_edit(course)
redirect_to root_url unless current_user == course.owner
end
It works fine, but I can't write a test for it! I wrote
class TeacherRightsControllerTest < ActionController::TestCase
def setup
#owner_1 = users(:owner_1)
#course = courses(:course)
#teacher_1 = users(:teacher_1)
end
test "teacher can't add teachers" do
log_in_as(#teacher_1)
assert_no_difference('TeacherRight.count') do
post :create, {teacher_right: {course_id:#course.id, user_id: #teacher_1.id }}
end
end
But it gives an error:
ActiveRecord::RecordNotFound: Couldn't find Course with 'id'=
app/controllers/teacher_rights_controller.rb:4:in `create'
As I can see,
post :create, {teacher_right: {course_id:#course.id, user_id: #teacher_2.id, }}
doesn't pass course_id in a way the controller can consume. How can I correct it?
You accidentally are posting the :course_id inside the :teacher_right hash, so it will show up as params[:teacher_right][:course_id] instead of the desired params[:course_id].
Your POST request should look like this:
post :create, {
course_id: #course.id,
teacher_right: { user_id: #teacher_1.id }
}

Rails 3 : How can I make auto grading system nested_form based online test system?

I'm still making online test program.
This is my model.
Survey
class Survey < ActiveRecord::Base
belongs_to :user
has_many :questions, :dependent => :destroy
accepts_nested_attributes_for :questions, :reject_if => lambda {|a| a[:content].blank?}, :allow_destroy => true
Question : there is is_correct column which indicates whether students get the right answer or not.
class Question < ActiveRecord::Base
belongs_to :survey
has_many :answers, :dependent => :destroy
accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
Answer : there is correct column which teacher checks making the survey(test), and there is user_answer column which students mark taking the test.
class Answer < ActviveRecord::Base
belongs_to :question
and I made taking exam interface on show.html.erb in survey views. So if students fill this check box and click the submit button, they can get their result page. but I can't show the result in the result page.
This is my survey controller.
def grading
#survey = Survey.new
#survey.user_id = current_user.id
if #survey.questions.answers.user_answer and #survey.questions.answers.correct
#survey.questions.is_correct = true
end
redirect_to results_surveys_path(#survey)
end
def results
end
The error message I saw is 'undefined method `answers' for []:ActiveRecord::Relation'. I thought that there were problem between question and answer table...
I thought auto grading part is easy, but I was wrong. I have no idea about this and I don't have any reference, except your help.
any idea welcome.
Thanks advanced.
Updated Question
Here is another question.
Now I can access to nested objects.(I think and I hope) but the result page(result.html.erb in survey views) can't show any result : "undefined method `name' for nil:NilClass".
result.html.erb
<h1><%= #survey.name %></h1>
<h3><%= #survey.user.username %></h3>
As I told in previous link, I have another form_tag in show.html.erb in survey views. then redirect to the result page with routes.rb.
resources :surveys do
collection do
get 'results'
end
end
I thought I can show the result page using is_correct column in question tables.
I didn't write anything in the result method in survey controller. Because when I redirect the page I wrote like this. Which means using #survey in result method, doesn't it?
redirect_to results_surveys_path(#survey)
Here is the result of rake routes.
seriousin#classcasts:~/ClassCasts$ rake routes | grep survey
results_surveys GET /surveys/results(.:format) surveys#results
surveys GET /surveys(.:format) surveys#index
POST /surveys(.:format) surveys#create
new_survey GET /surveys/new(.:format) surveys#new
edit_survey GET /surveys/:id/edit(.:format) surveys#edit
survey GET /surveys/:id(.:format) surveys#show
PUT /surveys/:id(.:format) surveys#update
DELETE /surveys/:id(.:format) surveys#destroy
surveys_grading POST /surveys/grading(.:format) surveys#grading
seriousin#classcasts:~/ClassCasts$
I think my basic idea caused all of my problem. Here is my survey controller.
class SurveysController < ApplicationController
def index
#surveys = Survey.all
end
def grading
#survey = Survey.new
#survey.user_id = current_user.id
#survey.questions.each do |question|
question.auto_check
end
redirect_to results_survey_path(#survey)
end
def results
#survey = Survey.where(params[:id])
end
def show
#survey = Survey.find(params[:id])
end
def new
#survey = Survey.new
end
def edit
#survey = Survey.find(params[:id])
end
def create
#survey = Survey.new(params[:survey])
#survey.user_id = current_user.id
end
def update
#survey = Survey.find(params[:id])
end
def destroy
#survey = Survey.find(params[:id])
#survey.destroy
end
end
as you can see, I'm using show page in survey views as another input form with grading method. I can use '#survey = Survey.new' in create method, it makes sense! but as I wrote in grading method, it generates another new survey, I think.
So I need to change that line. can you please help me?
Sending data
OK. when I submit in _form.html.erb in survey views, I can send data like this.
Parameters: {"id"=>"14", "survey"=>{"name"=>"The First Test!", "description"=>"This is the first test!", "questions_attributes"=>{"0"=>{"_destroy"=>"false", "id"=>"41", "content"=>"Question 2", "answers_attributes"=>{"0"=>{"_destroy"=>"false", "id"=>"66", "content"=>"Answer 2 of Question 2", "correct"=>"0"}, "1"=>{"_destroy"=>"false", "id"=>"67", "content"=>"Answer 1 of Question 2", "correct"=>"1"}}}, "1"=>{"_destroy"=>"false", "id"=>"42", "content"=>"Question 1", "answers_attributes"=>{"0"=>{"_destroy"=>"false", "id"=>"68", "content"=>"Answer 2 of Question 1", "correct"=>"0"}, "1"=>{"_destroy"=>"false", "id"=>"69", "content"=>"Answer 1 of Question 1", "correct"=>"1"}}}, "1376575795482"=>{"_destroy"=>"false", "content"=>"Question 3", "answers_attributes"=>{"1376575802879"=>{"_destroy"=>"false", "content"=>"Answer 1 of Question 3", "correct"=>"0"}}}}}, "commit"=>"Update Survey", "utf8"=>"✓", "authenticity_token"=>"/vNuB5Ck3QM5p+5ksL3tlmb+ti5nTA/qS96+vbPQkNw="}
This is OK. but because of form_tag in show.html.erb, show page contains another input form.
<%= form_tag({:controller => "surveys", :action => "grading"}) do %>
after submit again in show.html.erb I want to redirect to the results.html.erb with proper result. but there are errors like this.
Started POST "/surveys/grading" for 110.174.136.30 at Thu Aug 15 23:19:52 +0900 2013
Processing by SurveysController#grading as HTML
Parameters: {"utf8"=>"✓", "#<Answer:0x7fafd8c5f5a0>"=>"1", "#<Answer:0x7fafd95704a0>"=>"0", "#<Answer:0x7fafd9116a58>"=>"1", "authenticity_token"=>"/vNuB5Ck3QM5p+5ksL3tlmb+ti5nTA/qS96+vbPQkNw=", "#<Answer:0x7fafd8d03a38>"=>"0", "commit"=>"Submit", "#<Answer:0x7fafd8cfc580>"=>"1"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 12 LIMIT 1
Completed 404 Not Found in 3ms
ActionController::RoutingError (No route matches {:action=>"results", :id=>#<Survey id: nil, name: nil, description: nil, attempts: nil, user_id: 12, created_at: nil, updated_at: nil>, :controller=>"surveys"}):
app/controllers/surveys_controller.rb:31:in `grading'
Do you think that I need to change whloe answering mechanism?
Try like this, you are trying to call your method on an array. your have to iterate over it and then you can assign any value to it. you can do some re-factoring in your code as well to avoid loops.
in your grading method, do this:
def grading
#survey = Survey.new
#survey.user_id = current_user.id
#survey.questions.each do |question|
question.auto_check
end
redirect_to results_surveys_path(#survey)
end
In your Question model write a method auto_check like this:
def auto_check
answers.each do |answer|
is_correct = true if answer.user_answer and answer.correct
self.save!
end
end
I think it is a better approach.Thanks.
Updated Answer:
Whenever you try to pass an id in your path, that means that is a member function, if you are defining collection in your routes, that means you cannot pass an id in that(As you routes output shows. Have a keen look into it). change your routes like this:
resources :surveys do
member do
  get 'results'
end
end
Now you can access your url like this:
results_survey_path(#survey)
and in your results method :
def results
#survey = Survey.where(params[:id])
end
Hope, now it will work.

Resources