Rails: can't correctly invoke nested routes - ruby-on-rails

I have the following routes:
shallow do
resources :countries do
resources :airports
end
end
I'm having trouble with invoking two of the routes.
The airports_controller.rb file begins
def create
Rails::logger.debug "!!! Building airport with parameters #{params}"
#country = Country.find(params[:country_id])
Rails::logger.debug "!!! Found airport #{#country.name}"
#airport = #country.airports.build(params[:airport])
The last line gives the error Minitest::UnexpectedError: ActiveModel::ForbiddenAttributesError: ActiveModel::ForbiddenAttributesError, but the params I'm calling with are
!!! Building airport with parameters {"airport"=>{"iata_code"=>"CCC",
"icao_code"=>"CCCC", "name"=>"Airport 3", "city"=>"City 3", "latitude"=>"1.5",
"longitude"=>"1.5"}, "country_id"=>"980190962", "controller"=>"airports",
"action"=>"create"}
and as far as I can see all of those are in my permitted parameters:
def airport_params
params.require(:airport).permit(:iata_code, :icao_code, :name, :city, :latitude, :longitude, :notes, :country_id)
end
Secondly, my airports\_form.html.erb begins <%= form_for [#country, #airport] do |f| %>, but that gives an error Minitest::UnexpectedError: ActionView::Template::Error: undefined method 'airports_path' for #<#<Class:0x4b0ba30>:0x55a2e48>. Yes, that path is undefined, but I was trying to get to path country_airports_path, which is defined.
So what am I missing here?

In "def create"
def create
...
#airport = #country.airports.build(airport_params)
You've correctly created the "airport_params" function, however you're not calling it. That should explain the ForbiddenAttributesError.
As for the undefined method, at first glance it looks like an incorrect association, are your models correctly related (i.e. belongs_to and has_many)? If so, you could try adding url: country_airports_path to the form_for field.

Attributes
The attributes error you've received is basically caused by your non-use of strong_params.
Rails 4 introduced strong_params to give you the ability to define specific attributes to pass to your model (prevents mass assignment). The problem you have is that you're using the Rails 3 way - passing all of the attributes without whitelisting them
As pointed out, you'll be better doing this:
#app/controllers/airports_controller.rb
Class AirportsController < ApplicationController
def create
#airport = Airport.new(airport_params)
end
private
def airport_params
params.permit(:airport).permit(:your, :params).merge(country_id: params[:country_id])
end
end
However, I believe there's something deeper you need to consider.
You're currently using .build for your airport model object. Whilst there's nothing wrong with this, you need to consider what you're trying to achieve...
The Airport model object is a standalone object. You can easily associate it with your Country model by setting the foreign_key in your strong params (demonstrated above)
If you use this line: #airport = #country.airports.build, you're working with the Country object, which IMO will open yourself up to errors down the line. As mentioned, there's nothing "wrong" with what you're doing; it's just that I'd either work with the Airport model directly (as written above), or use accepts_nested_attributes_for to work with the Country model
--
Route
Secondly, your path error is going to be caused by your form_for
form_for builds a form from an ActiveRecord object - which it does by taking arguments such as model_name to build the "url" / "action" for the form.
This means every time you populate a form_for object with an ActiveRecord object (variable), Rails will build a route based off of what you pass it (it does not know whether the object is nested or not)
<%= form_for #airport do |f| %>
# builds route using "airport_path"
<% end %>
If you want to create a nested form, you'll be much better using an array of ActiveRecord objects, as to provide Rails with the knowledge you're using a nested resource:
<%= form_for [#country, #airport] do |f| %>
# builds route using "country_airport_path"
<% end %>

Related

Rails 5 Create Multiple Records of Same Model in One Form, Unknown Attribute Error

I need to make a form that can handle the creation of multiple records of the same model, and submit the information in a way that passes through Rails’ Strong Parameters. On the front-end, I dynamically add two new text fields for puppy name and breed and want to save them all to a form. But I'm having trouble passing the array of puppy name and breed through Strong Params; getting
unknown attribute ‘puppies’ for Puppy
I think it's something very simple I'm overlooking. Please help generate these puppies!
new.html.erb
<%= form_tag puppies_path do %>
<%= text_field_tag “puppies[puppies][]puppy[name]” %>
<%= text_field_tag “puppies[puppies][]puppy[breed]” %>
<%= submit_tag “Add Puppy” %>
<% end %>
which generates these parameters
"puppies"=>{"puppies"=>[{"puppy"=>{"name"=>"ribbon", "breed"=>"poodle"}}]}
and the Strong Params in the controller
class PuppiesController < ApplicationController
def index
#puppies = Puppy.all
end
def new
end
def create
puppies_array = puppies_params[:puppies].each do |puppy|
puppy[:puppy]
end
Puppy.create(puppies_array)
end
def show
#puppy = Puppy.find(params[:id])
end
private
def puppies_params
params.require(:puppies).permit({
puppies: [
puppy: [
:name,
:breed
]
]
})
end
end
Any ideas what I’m missing?
Pseudo code of what I want to pass through:
[(name, breed), (name, breed), (name, breed)]
How can I write it correctly? I'm following this tutorial https://medium.com/#dmccoy/how-to-build-html-inputs-to-submit-an-array-of-hashes-with-a-rails-form-tag-that-work-with-strong-f70a6c03d78e
You haven't added rest of your controller code, but I am assuming you are using something like: Puppy.create(puppies_params)
But since you are nesting the params in your view, you will need to use Puppy.create(puppies_params[:puppies]) to ensure you are able to pass in the correct array of name and breed into the create action.
Edit
Since puppies array contains a hash with puppy key, we will need to extract those to get the final array of puppy attributes.
def create
puppies_array = puppies_params[:puppies].collect do |puppy|
puppy[:puppy]
end
Puppy.create(puppies_array)
end
My comment below has a shorthand for setting puppies array, you can use whichever version looks more readable and understandable to you
To get to the array of attributes that you want, you can do this:
puppies_array = puppies_params[:puppies][:puppy].map{|puppy| puppy.values}
This takes the hash puppies within puppy params, and takes the array of hashes in puppy and then takes just the value part of the hashes in that array.
You'll get your pseudo-code result.
For info, note that puppy in the map block has no special significance... it just indicates the instance of the array. You could just as easily do
puppies_array = puppies_params[:puppies][:puppy].map{|some_dog| some_dog.values}

Rails Undefined Method Path for New

I am relatively new to rails but having a real problem with something that I know should be really simple. I have a model called channel, in it I have a simple new method, in the view I have form but every time I try and load it, I get an error to say:
undefined method `channels_path'
My view (new.html.erb) is really simple, for the minute it just has a button in it with a name and a value, it just looks like this:
<%= simple_form_for #channel do |f| %>
<%= f.error_notification %>
<%= f.button :submit, 'Free Plan', name: 'plan', value: 'free' %>
<% end %>
My Controller has:
def new
#channel = Channel.new
end
And in my routes I have:
resources :channel
Output form a rake routes is:
channel_index GET /channel(.:format) channel#index
POST /channel(.:format) channel#create
new_channel GET /channel/new(.:format) channel#new
edit_channel GET /channel/:id/edit(.:format) channel#edit
channel GET /channel/:id(.:format) channel#show
PATCH /channel/:id(.:format) channel#update
PUT /channel/:id(.:format) channel#update
DELETE /channel/:id(.:format) channel#destroy
Which all looks how I expect. But as the error says there is no channels_path, but as far as I am aware, there shouldn't be.
I am sure this is supposed to be really simple but I just cannot see what I am doing wrong. Can anybody help?
Many thanks
David
EDIT
I have updated the route to be:
resources :channels
I can now load the form, however I now get the error when trying to submit it:
param is missing or the value is empty: channel
Being caused by:
# only allow specific params
def channel_params
params.require(:channel).permit(:name,
:slug,
:description,
:plan,
:subscription_ends
)
end
I am assuming singular is correct here based on the model, but have tried plural too with no luck. Any more thoughts?
Many thanks
Edit
Got it working in the end, it appears you have to have at least one input in your form. I added an input for the name field and it started working.
Many thanks to everyone that commented
According to your rake task, the path should be
channel_path
If it's not working with the simple_form_for helper, it's probably because you should have set up your routes as resources: channels
UPDATE
The new bug is coming from nothing being received by the controller for :channel
Try adding a field like so
f.hidden_field :plan, :value => "free"

matching POST route rails 4

I am trying to upload a photo but after I press the upload button, I get this error. I am new to rails 4 so I'm not sure what I am missing.
My logic is when I click the submit button. This will cause the create action to fire and create a IncomePicture object and store it in my database.
No route matches [POST] "/income_pictures/new"
Routes:
root_path GET / static_pages#home
income_pictures_path GET /income_pictures(.:format) income_pictures#index
POST /income_pictures(.:format) income_pictures#create
new_income_picture_path GET /income_pictures/new(.:format) income_pictures#new
edit_income_picture_path GET /income_pictures/:id/edit(.:format) income_pictures#edit
income_picture_path GET /income_pictures/:id(.:format) income_pictures#show
PATCH /income_pictures/:id(.:format) income_pictures#update
PUT /income_pictures/:id(.:format) income_pictures#update
DELETE /income_pictures/:id(.:format) income_pictures#destroy
Controller:
class IncomePicturesController < ApplicationController
def new
#income_picture = IncomePicture.new
end
def create
#income_picture = IncomePicture.new(IncomePicture_params)
if #income_picture.save
flash[:notice] = "Income picture successfully uploaded"
redirect_to #income_picture
end
end
def show
#income_picture = IncomePicture.find(params[:id])
end
def index
#income_picture = IncomePicture.all
end
private
def IncomePicture_params
params.require(:income_picture).permit(:image, :name)
end
end
View:
<%= form_for :income_picture, :html => { :multipart => true } do |f| %>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :image %>
<%= f.file_field :image %>
</p>
<p><%= f.submit %></p>
<% end %>
I think you want form_for #income_picture rather than form_for :income_picture.
From the form guide: Using a symbol creates a form to new_income_picture_path, i.e. /income_picture/new whereas using a populated instance variable creates a form to income_pictures_path, i.e. income/pictures. Both set the form's method to POST. However, there's no such route as POSTing to /income_picture/new/, which is what caused the error.
form_for
To elaborate on the accepted answer, you have to remember that when calling form_for, Rails does some pretty amazing things:
It takes an ActiveRecord object and builds a "route" out of it (from the model)
It populates the form with the ActiveRecord object's data
It allows you to retain a perceived persistent state on the form (by perpetuating the data)
The problem you have is you're passing a simple symbol to the form - which prevents Rails from being able to accurately access the data required to make the 3 "magic" steps above possible.
This means you'll get random errors like the one you're seeing (IE in the absence of an ActiveRecord object, Rails will just use the same URL that you have on your page - /new)
--
ActiveRecord
The way to fix the issue you have is to replace the symbol with an ActiveRecord object, which was suggested in the accepted answer.
The reason why using an ActiveRecord object (#instance_variable) works is because of Ruby's core functionality -- it's a object orientated language. Being object orientated, it means that each time you populate an ActiveRecord object, you'll basically give Rails a series of other information, such as model_name etc.
This means when you pass the #instance_variable to the form_for method, Rails will be able to take the data from ActiveRecord & process it on screen for you

how form_for works in Ruby on Rails

I am an newbie. I have read the API documentation. But still don't understand how form_for works.
Firstly, from Ruby on Rails Tutorial, the form for follow button:
<%= form_for(current_user.relationships.build(followed_id: #user.id)) do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
I understand current_user.relationships.build(followed_id: #user.id) means a new record. But why can we not just submit and trigger controller to save the record without hidden_field? Why do we still need to post followed_id to controller?
Secondly, in hidden_field, what does :followed_id means? I believe that is a symbol, i.e. it equals only "followed_id" not a variable of id. If that is only the name of the input field, then what is its value?
Thirdly, how does form_for know where the submission should be sent to? Which controller and action the form_for will post to?
Fourth, how does params work with form_for? In this follow button case, params[:relationship][:followed_id] will return #user.id in controller. How does it know the first hash attribute is :relationship? We have neither mentioned form_for :relationship nor form_for #relationship.
I know these questions can be very dumb, but I am really stuck. Any help will be appreciated.
I didnt do that tutorial so mind me if i dont answer directly to your question.
Take a look at the rails guide about form helpers and it explains in details your questions, probably in a more articulate way than i can.
form_for(path/to/your/controller/action) is a helper method to create HTML form elements with the url path to the POST or GET request. The helper knows if it should be a new record or an update record based on what you are asking to do in your controller action.
For example
In your controller
def new
#my_instance_variable = Myobject.new
end
In your view new.html.erb
<%= form_for #my_instance_variable do |f| %>
...
<% end %>
In your case the logic was directly written in the helper and you could also directly write
<%= form_for Myobject.new %>
Both will result with the following html
<form action="/myobjects/new" method="post">
# in this case rails knows its a `POST` request because the route new action
# is by default a POST request. You can check these routes and their request
# by using `rake routes` in terminal.
Then the hidden_field is another helper to contain a value, in your case the #user.id that will be passed as parameter then saved as a Create or update action for the given object. The reason it doesnt add the value in the hidden field tag is because you already have a model association that knows the id of user since the link of form uses the build method with user id.
Last part you need to understand the form_for link logic
current_user.relationships
# implies the association of the current_user has many relationships
current_user.relationships.build
# .build is a method to populate a new object that can be save as a new record
# means you will create a new relationship record by populating the user_id
# column with the current_user.id and the followed_id with the target #user.id
After reading the book The Rails 4 Way, I understand form_for better now.
11.9.1.5 Displaying Existing Values.
If you were editing an existing instance of Person, that object’s attribute values would have been filled into
the form.
in this way, when we build the relationship by usingcurrent_user.relationships.build(followed_id: #user.id), the relationship instance will be created and gain attribute followed_id. So that, instead of "creating" a relationship, we are actually editing the relationship by the form.
Then Rails will know you are editing and load the existing attribute "followed_id" to the field. Therefore, we don't need to assign value to the field like using f.hidden_field :followed_id, value: #user.id.
And the reason why we have to use a field to pass followed_id to params is because HTTP server is stateless, it doesn't remember you are creating a relationship with which user.
One of the advantages of writing form_for current_user.relationships.build(followed_id: #user.id) instead of standard form_for #relationship is we don't need to write "if-condition" in controller like this:
unless current_user.nil?
if current_user.following?(#user)
#relationship=current_user.relationships.find_by(followed_id: #user.id)
else
#relationship=current_user.relationships.new
end
end
params will be sent to the controller which belongs to the instance's model. "post" method will go to action create, "delete" will go to destroy, "patch" will go to update, etc.
params will be a hash with another hash inside like { instace_name: { field_1: value1, field_2:value2 } } or full params as below
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"afl+6u3J/2meoHtve69q+tD9gPc3/QUsHCqPh85Z4WU=",
"person"=>{"first_name"=>"William", "last_name"=>"Smith"},
"commit"=>"Create"}

Rails: How to store form params in a non-active record model?

I want to do store the parameters from a form in a model. Since I don't want the model to use any database, it does not inherit from ActiveRecord::Base. I'm thinking it should look something like this:
# in view:
<% form_for :question, :url => {:action => "ask"} do |f| %>
<%= f.text_field(:q) %>
<%= submit_tag %>
<% end %>
# in controller:
def ask
# I want this to magically set all variables in #question using
# values from params.
#question = params[:question]
end
# in model:
class Question
attr_accessor :q
def initialize
#q = ""
end
end
But after spending 1½ days on it, it doesn't seem to be the right way to do it. Any suggestions would be much appreciated.
Take a look at this article:
http://pullmonkey.com/2008/1/6/convert-a-ruby-hash-into-a-class-object
It shows how to create a class that will dynamically create a class from the passed in Hash.
Even if you set your Question properly, how do you plan to persist this? A file?
I think it is a much better approach to get a deep understanding of ActiveRecord before going for fancy models that have custom persistence
You might want to check out Ryan Bates' Railscast on creating a non ActiveRecord model
http://railscasts.com/episodes/121-non-active-record-model
... however I'd suggest that if you're thinking RESTfully about this, it sounds from your comment to Sam's answer like you may have another RESTful resource at work - i.e. you don't actually want to use a QuestionsController... but instead something to do with what you're actually creating (the method call you mention). You can still initialize your Question object as part of that process.

Resources