I'm trying to implement a has_one relationship through a basic form and am getting completely stuck.
I seem to have tried huge numbers of combinations of things in controllers and forms but don't seem to be getting anywhere.
I have a phone number for which I use an external service populated by an api.
Eventually I would like to have all the phone numbers of this type set up for this, but right now I'd be happy with getting just one working.
parent.rb
has_one :clever_phone
And a phone model
clever_phone.rb
attr_accessible :number, :parent_id, :prefix, :country_iso
belongs_to :parent
Controller
clever_phones_controller
def new
#parent = Parent.find(params[:parent_id])
#clever_phone = #parent.build_clever_phone
def create
#parent = Parent.find(params[:parent_id]
#clever_phone = Parent.create_clever_phone(params[:clever_phone])
View
I really don't want to do a nested form from the parent here as I have some complicated code which generates information used in the creation through an api that sits, correctly I think, in the clever_phones_controller
#simple_form_for #clever_phone do |f|
= f.input :country_iso, as: :country, :iso_codes => true, priority: ["gb"]
= f.input :prefix
= f.submit
Routes
resources :parent do
resource :clever_phone
end
My issue is that the params for #parent don't seem to be being passed to the create method in the controller and thus I get a
'Can't find parent without ID'
error
If I try it as a nested form
I get
NoMethodError undefined method 'parent_clever_phones_path'
When in fact I've not used that, only parent_clever_phone_path (note the absence of plural).
I'm sure this should be really simple but I've stared at it for hours now and am at the end of my tether...
Any ideas?
Okay, there are several issues which you need to contend:
--
Variable
The reason why #parent params are not being passed is because you've not included them in your form. This is where nesting your forms would come in very handy - passing your parent params, and your clever_phone params in a single call
Since you don't want to do this, I would recommend at least populating a hidden field with the parent_id that the clever_phone is associated with:
#app/views/clever_phones/new.html.erb
= simple_form_for #clever_phone do |f|
= f.input :parent_id, as: :hidden, value: params[:parent_id]
= f.input :country_iso, as: :country, :iso_codes => true, priority: ["gb"]
= f.input :prefix
= f.submit
--
Route
Alternatively, you have to make sure you're able to create the correct route for your form (as I think the #parent = Parent.find params[:parent_id] is not being called on your create method:
#app/views/clever_phones/new.html.erb
= simple_form_for [#parent, #clever_phone] do |f|
This will give you the ability to point your create action to the nested version of your route; hence providing the parent_id param for you
--
Having written, this I don't think it will address the core of your issue. If you need more information, please write a comment & I'll happily work to create a more appropriate answer!
Right: Worth putting in the solution that finally worked.
Thanks to Rich Peck for getting me halfway there...
model
parent.rb
has_one :clever_phone
clever_phone.rb
belongs_to :parent
view
simple_form_for #clever_phone, url: parent_clever_phone_path(#parent) do |f|
-----
f.input :parent_id, :value: #parent.id
-----
routes
resources :parent do
resource :clever_phone
controller
def new
....
#parent = Parent.find(params[:parent_id])
#clever_phone = #parent.build_clever_phone
def create
#parent = Parent.find(params[:parent_id]
#clever_phone = CleverPhone.new(params[:clever_phone]
Unless you do build_clever_phone the parent_id won't populate. I couldn't get #parent.create_clever_phone to work, but this seemed to do the trick...
Hope that saves somebody a days work...
Related
I have a permit and vehicle model. I am trying to update the AA create controller to work how I have it in my rails app. That is taking the vehicle license_number entered and inputting it into the permit table, then also taking the inputted permit_id and inputting it into the permits attribute of the vehicle it is related to in the vehicle table.
admin/permit.rb
permit_params :permit_id, :vehicle, :date_issued, :issued_by, :date_entered, :entered_by
form do |f|
f.inputs do
f.input :permit_id
f.input :vehicle, :collection => Vehicle.all.map{ |vehicle| [vehicle.license_number]}
f.input :date_issued, as: :date_picker
f.input :issued_by
end
f.actions
end
controller do
def new
#permit = Permit.new
#vehicle = #permit.build_vehicle
#vehicle = Vehicle.all
super
end
def create
vehicle = Vehicle.find_by(license_number: permit_params[:vehicle_attributes][:license_number])
#permit = current_user.permit.build(permit_params.merge(date_entered: Date.today,
entered_by: current_user_admin.email))
super
end
end
My errors that I am getting, is that it is inputting the license_number in for the permit_id and then it is also saying the permit_params is not a defined variable. Any help would be great, thanks!
You have an interesting case here: it is confusing because you have a model called Permit, and usually in Rails you name the params method something like permit_params. Turns out, permit_params is the general method signature for ActiveRecord to implement strong params: https://activeadmin.info/2-resource-customization.html
With that, instead of calling permit_params in your create action, you need to call permitted_params[:vehicle_attributes][:license_number]. That’s why it’s considering permit_params as an undefined variable. Again, those two method signatures will be the same for all your ActiveAdmin forms.
Additionally, I’m not sure if this is a typo but you define #vehicle twice in your new method. I’m not sure you need to build a vehicle for the permit form unless you’re doing nested forms. Either way, I think the last line should read #vehicles = Vehicle.all Then you can use that in your form, which also could use an update in the collection part of your form field:
form do |f|
f.inputs do
f.input :permit_id
f.input :vehicle, collection: #vehicles.map { |vehicle| [vehicle.license_number, vehicle.id] }
f.input :date_issued, as: :date_picker
f.input :issued_by
end
f.actions
end
The collection_select form tag will take the first item in the array as the value that appears in the form, and the second value will be the value passed through in the params (https://apidock.com/rails/ActionView/Helpers/FormOptionsHelper/collection_select).
Then in your create action, you can find the Vehicle with the id:
Vehicle.find(permitted_params[:vehicle_attributes][:vehicle_id])
I would avoid Permit as a model name, try using VehiclePermit.
I have a Message data model in Rails with a "virtual" association. That is, a method that returns a collection of "associated objects", but it is not an actual association in the ActiveRecord sense.
class Message
def recipients
#recipients
end
def recipients=(arr)
#recipients = arr
end
end
The thing with simple_form is that, when I try to show a association field, it fails because there's no :recipients association:
= simple_form_for(#message) do |f|
= f.association :recipients, collection: Recipient.all
The solution seemed then simple, I went on to use a f.input with the as: :select option:
= simple_form_for(#message) do |f|
= f.input :recipients, as: :select, collection: Recipient.all
And this works fine, except for the fact that it does not automatically detect the values already in #message.recipients so that the elements appear pre-selected when the form is rendered.
If Message#recipients were an actual association, in the ActiveRecord sense, then f.association in the form would do this as well. But for reasons that go beyond the scope of this question, I can't make it as an actual association.
The question then is, can I achieve f.input :recipients, as: :select to pre-select the selected elements?
Well, this is embarrassing. Just after posting this, I came up with an idea that ended up solving the problem:
class Message
# ...
def recipient_ids
# ...
end
def recipient_ids=(arr)
# ...
end
end
I added the recipient_ids getter and setter, in addition to the association getter and setter that I had before.
Then I updated the form input field to refer to :recipient_ids instead of :recipients:
= simple_form_for(#message) do |f|
= f.input :recipient_ids, as: :select, collection: Recipient.all
Issue: Instead of updating nested attributes, they are being created on top of the existing nested attributes when I hit the #update action of the associated features_controller.rb
Likely Cause: I think the problem lies in my lack of understanding in Rails' form_for. I think the breakdown is in my views, how I render the persisting nested attributes, and/or how I fail to specify the nested attribute's id, causing it to simply create a new one
feature.rb
class Feature < ActiveRecord::Base
...
has_many :scenarios
accepts_nested_attributes_for :scenarios,
allow_destroy: true,
reject_if: :all_blank
...
end
features_controller.rb
def update
...
project = Project.find(params[:project_id])
#feature = Feature.find(params[:id])
if #feature.update_attributes(feature_params)
# checking feature_params looks good...
# feature_params['scenarios'] => { <correct object hash> }
redirect_to project
else
render :edit
end
end
...
private
def feature_params
params.require(:feature).permit(:title, :narrative, :price, :eta, scenarios_attributes[:description, :_destroy])
end
_form.html.haml (simplified)
= form_for [#project, #feature] do |f|
...
- if #feature.new_record? -# if we are creating new feature
= f.fields_for :scenarios, #feature.scenarios.build do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
- else -# if we are editing an existing feature
= f.fields_for :scenarios do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
I'm sure there's a nicer way to achieve the if #feature.new_record? check. I'm also using a few Javascript hooks to create dynamic nested attribute forms (which I've left out), heavily influenced by Railscast #196 Nested Model Form (revised)
I would love a really nice Rails-y implementation of dealing with these sorts of nested forms.
Try adding :id to the :scenario_attributes portion of your feature_params method. You only have the description field and the ability to allow a destroy.
def feature_params
# added => before nested attributes
params.require(:feature).permit(:id, :title, :narrative, :price, :eta, scenarios_attributes => [:id, :description, :_destroy])
end
As #vinodadhikary suggested, you no longer need to check if feature is a new record, since Rails, specifically using the form_for method, will do that for you.
Update:
You don't need to define if #feature.new_record? ... else in your form. It will be taken care by Rails when you use form_for. Rails checks if the action is going to be create or update based on object.persisted?, so, you can update your form to:
= form_for [#project, #feature] do |f|
...
= f.fields_for :scenarios, #feature.scenarios.build do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
As #Philip7899 mentioned as a comment in the accepted answer, allowing the user to set the id means that they could "steal" children records belonging to another user.
However, Rails accepts_nested_attributes_for actually checks the id and raises:
ActiveRecord::RecordNotFound:
Couldn't find Answer with ID=5 for Questionnaire with ID=5
Basically the ids are looked for in the children association (again, as said by #glampr). Therefor, the child record belonging to another user is not found.
Ultimately, 401 is the response status (unlike the usual 404 from ActiveRecord::RecordNotFound)
Follows some code I used to test the behaviour.
let :params do
{
id: questionnaire.id,
questionnaire: {
participation_id: participation.id,
answers_attributes: answers_attributes
}
}
end
let :evil_params do
params.tap do |params|
params[:questionnaire][:answers_attributes]['0']['id'] = another_participant_s_answer.id.to_s
end
end
it "doesn't mess with other people's answers" do
old_value = another_participant_s_answer.value
put :update, evil_params
expect(another_participant_s_answer.reload.value).to eq(old_value) # pass
expect(response.status).to eq(401) # pass
end
In conclusion, adding the id to the permitted params as stated above is correct and safe.
Fascinating Rails.
I have a Parent model named "Controller" (Mature app, and not my decision)
belongs_to :controller
accepts_nested_attributes_for :controller
Form:
= f.fields_for :controller do |c|
= c.hidden_field :id, :value => #controller.id
= c.text_field :slw_type
which doesn't get displayed.
= f.fields_for :literally_anything_else do |c|
= c.hidden_field :id, :value => #controller.id
= c.text_field :slw_type
if change the variable name to anything else, the form builds. I have a hunch that it's a rails specific reserved name.
Question:
What's the problem? and how can I make this work?
SOLVED:
The issue was that the parent model wasn't associated with the child model yet. My mistake for not providing all the information necessary.
This worked.
def new
#controller = Controller.find(params[:controller_id])
#inspection = Inspection.new(:controller => #controller)
Therefore my fields_for form builder also worked.
Pick some innocuous variable name. not_really_a_controller or whatever. Use that for your variable and your form. Then, in your actual controller (e.g. ActionController::Base descendent), rename the incoming param so the model doesn't know any different, like so:
before_filter :filter_params
private
def filter_params
if params[:not_really_a_controller]
params[:controller] = params.delete(:not_really_a_controller)
end
end
I've used this strategy for similar reasons in the past, though not specifically for controller. Worth a try though!
I am still struggling both writing the controller and the actual form to be able to nest one form in another with an optional model?
I have Message which has many contacts
When submitting a message, I want to add a contact optionally.
I have this as an example:
= simple_form_for Message.new, :remote => true do |f|
#message_form
= f.error_messages
%p
= f.input :account_name, :url => autocomplete_account_name_messages_path, :size => 40, :as => :autocomplete
%p
= f.input :topic, :required => true,
:input_html => {:size => 30}
#add_contact_btn
= link_to "Add Contact"
#contact_form
= f.simple_fields_for :contactd do |fc|
= fc.input :email
= fc.input :first_name
= fc.input :last_name
= f.submit 'Give'
= f.submit 'Request'
For Message.rb model, I have the following:
has_many :contacts
accepts_nested_attributes_for :contacts, :reject_if =>:all_blank
Note: When I used :contacts in the simple_fields_for it didn't work, so it is singular. But the reverse for accepts_nested_attributess_for.
In my create controller for message, I included message.contacts.build
But right now I am still generating nil contacts.
Here is what I see passed as form data from google chrome:
message%5Baccount_name%5D:McKesson
message%5Btopic%5D:testing a contact
message%5Bbody%5D:testing this
sender_id:
receiver_id:23
message%5Bcontacts%5D%5Bemail%5D:888#gmail.com
message%5Bcontacts%5D%5Bfirst_name%5D:Ang
message%5Bcontacts%5D%5Blast_name%5D:Name
The correct method name is simple_fields_for (notice the plural)
Also, you need to keep the f. to call it on the simple_form object
I have a small project where I demonstrate how to use nested forms in simple-form, combined with cocoon (a gem I created to add/remove nested elements dynamically).
The project is on github.
Hope this helps.
In my create controller for message, I included message.contacts.build
But right now I am still generating nil contacts.
Make sure you put in your Message.rb model the ability for it to accept the attributes too.
class Message < ActiveRecord::Base
attr_accessible :contacts_attributes
has_many :contacts
accepts_nested_attributes_for :contacts
I know it doesn't answer your question fully but it may have just been this. When it comes to my project, it would return nil if i didn't include the :contacts_attributes, in my case it deals with products. Hope this helps even if I'm not using simple form as of now!
I faced similar issues working with nested forms. As suggested by JustinRoR you need to define
attr_accessible: contacts_attributes.
You should be able to test the hash in the ruby console ( I am not sure if you have tried this). I suggest you to print the params[:message] and use this to create message from console like Message.new(params[:message]). (Note params[:message] is what you get by printing the params[:message] hash).
Once it works in console it should work like charm