Rails nested form with accepts_nested_attributes_for with an unfortunate model name - ruby-on-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!

Related

has_one relationship without nested_form

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...

an example of a nested form in simple form?

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

Retain Search Form Data Ruby On Rails

Trying to build a search on my homepage with simple_form (Pretty much same as formtastic). The search works fine and im getting my results but after submission I want to retain the vales with what the user submitted.
I am using a namespace for my form so how can I retain the data for the form. Here is some code which may help.
Controller
def index
#results = Property.search(params[:search])
end
View
%h1 Search Form
= simple_form_for(:search) do |f|
= f.input :location, :as => :select, :collection => Location.all.asc(:name)
= f.input :type, :collection => PropertyType.all.asc(:name)
= f.input :bedrooms, :collection => 1..10,
%p.box
= f.button :submit
-if #results
%h1 Search Results
.results
- #results.each do |property|
.result
%h1= property.title
Within the Index controller I have tried all sorts of things ie
#search = params[:search]
But each time I try something the search breaks.
What am I doing wrong ?
Hope you can advise
One approach is to do as Xavier Holt suggested, and pass in values to each input. The simpleform doco suggests:
= f.input :remember_me, :input_html => { :value => '1' }
The other approach is to have simpleform do it for you. SimpleForm will automatically populate the fields with values if you give it something like an activerecord object.
In this case, that means creating a model object:
class PropertySearchCriteria
attr_accessor :location, :type, :bedrooms
def initialize(options)
self.location = options[:location]
self.type = options[:bedrooms]
self.bedrooms = options[:bedrooms]
end
end
Then, change your controller:
def index
#property_search_criteria = PropertySearchCriteria.new(params[:search])
#results = Property.search(#property_search_criteria)
end
(you'll have to change the Property.search method as well)
Then, change your simple_form_for:
= simple_form_for(:search, #property_search_criteria) do |f|
And if you do all that, and get the stars to align just right, then simpleform will pre-populate the form fields all by itself. You may have to add some stuff to PropertySearchCriteria to get simpleform to be properly happy.
This is a lot of stuffing around just to get the values showing up, but it'll keep you sane if you need to add validations.
I'm doing something similar in the app I'm working on (I'm not using formtastic, but this should be at least very close to something that works for you). I got around it by making sure #search was a hash in the controller:
#search = params[:search] || {}
And then using #search[:key] as the :value option in all my search inputs (There's a chance you'll need to set #search.default = '' to get this working):
<%= text_field_tag :name, :value => #search[:name] %>
And that's all it took. As my app is getting more complicated and AJAXy, I've been thinking of moving the search parameters into the session information, which you might want to do now to stay ahead, but if you're just looking for a simple solution, this worked great for me.
Hope this helps!
you can try storing your parameters in session like so:
def index
#results = Property.search(params[:search])
store_search
end
def store_search
session[:search] = params[:search]
end
just be sure when you are done with the parameters that you clean them up
...
clear_search if session[:search]
def clear_search
session[:search] = nil
end

Ruby/Rails: dynamically change attribute in shared partial

This should be a layup for someone...
I'm trying to change a form field's attribute depending on which controller/model is calling the partial containing the form fields...
The issue (below) is with parent_id... which references one of two columns in a dogs table. It needs to either be kennel_id or master_id depending on which view this partial is being rendered in.
Not comfortable enough, yet, with Ruby/Rails language/syntax/tools to dynamically change this without getting bogged down in if/else statements.
I'm calling a shared partial and passing in a local variable:
= render "dogs/form", :parent => #kennel
or
= render "dogs/form", :parent => #master
In the partial I'd like to:
= form_for ([parent, target.dogs.build]) do |f|
= render "shared/error_messages", :target => parent
.field
= f.label :name
= f.text_field :name
.field
= f.hidden_field :parent_id ### <= PROBLEM
.actions
= f.submit 'Save'
Just thinking out loud:
I don't know if the parent-models have the proper names for it, but you could do something like:
= f.hidden_field "#{parent.class.name.underscore}_id"
But that doesn't look right. So, why not pass it as an argument?
= render "dogs/form", :parent => #master, :foreign_key => :master_id
Or, create aliases on the dog model to handle some sort of dynamic delegation:
class Dog
def parent_id=(parent_id)
case parent.class
when Master then self.master_id = parent_id
when Kennel then self.kennel_id = parent_id
end
end
def parent_id
case parent.class
when Master then self.master_id
when Kennel then self.kennel_id
end
end
end
But that sucks too. Could the relation be polymorphic? Then you can leave out the switching.
class Dog
belongs_to :owner, :polymorphic => true
end
= f.hidden_field :owner_id
Just some ideas. Hopefully one of them makes sense to you...
Wow, my initial answer wasn't even close. I think what you'll want is a polymorphic association: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations This way, the parent can be whatever class you need it to be.

Helper method or Model or Controller?

Say I have a form_for with a select menu to assign a User on a belongs_to association:
...
form.select :user_id, #users, :prompt => "Select a User"
...
Currently I have #users in the controller as follows:
#users = User.all.map { |u| [u.full_name, u.id] }
I feel like this logic should maybe moved into a helper or even to the model.
But I am confused as to where to deal with this and how.
The general answer depends on how often you're going to use it:
helper: used often but only in views or controllers
model: used often anywhere the model can be used (other models)
controller: used rarely and only for specific actions.
However in your case, the answer is none of the above, and stop trying to reinvent the wheel. About 95% of the things people try to do with Rails are tasks that others have already done. There's a very good chance that it's either in the Rails Core or exist in either gem or plugin form.
What you're trying to do has already been done, and is built into the Rails core. It's a ActionView::Helpers::FormOpitionsHelper method called collection_select
collection_select does exactly what you want to do, it's also much more robust than a single purpose method.
It has the form of
collection_select(object, method, collection, value_method,
text_method, select_options = {}, html_options)
value_method and text_method are sent to each item in collection to get the select value and the display text for each select option. It is not required that either are column names.
Use it like this:
<% form_for #whatever do |form| %>
<%= form.collection_select :user_id, User.all, :id,
:full_name, :prompt => "Select a User" %>
<% end %>
You should put this in a model as it's logic oriented and by the way you should never do
#users = User.all.map { |u| [u.full_name, u.id] }
but
#users = User.all(:select => "full_name, id")
and if full_name is a method, something like that :
#users = User.all(:select => "last_name, first_name, id").map{|u| [User.full_name(u.first_name, u.last_name), u.id]}
That would be a model method since it has logic for the model. Helper methods should have UI level logic (whether to display a link or not) and HTML helpers (methods to generate links for example)
i think moving that to a helper is the best thing, since it just does help you with creating options for a select box, which is UI level.
But unless you would use that piece of code again, then to the model it must go! :)
The model:
def self.select_display
all(:select => "id, first_name, last_name").map { |u| [u.name, u.id] }
end
The view:
select :user_id, User.select_display
I had a similar problem and ended up using a module, in order to stay as DRY as possible (most of my models had a name and an id)
The module looked like this:
#lib/all_for_select.rb
module AllForSelect
def all_for_select(permission = :read)
#used declarative authorization for checking permissions
#replace first line with self.find(:all, if not using it
with_permissions_to(permission).find( :all,
:select =>"#{table_name}.id, #{table_name}.name",
:order => "#{table_name}.name ASC"
)
end
end
On your model, you just extend the module:
class Client < ActiveRecord::Base
extend AllForSelect
...
end
On your controller, you can call Client.all_for_select. I usually do this on a before_filter
class SalesController < ApplicationController
before_filter :fill_selects, :only => [:new, :edit, :update, :create]
...
private
def fill_selects
#clients = Client.all_for_select
end

Resources