Updating dependent form content using AJAX...passing form variable - ruby-on-rails

How can I pass a form variable to a template that is only used by AJAX to load dynamic data into a section of a form?
I have a Course form that belongs_to a lesson, and a lesson has_many gradable_items. If the lesson select input is changed...the nested fields for the gradable_items must update to reflect the chosen lesson.
jQuery ->
l = $("#course_lesson_id")
l.on "change", ->
$("#gradable_items_container").load("gradable_items_inputs?lesson=#{l.val()}")
The above scripts works. It loads the code in gradable_items_inputs.html.erb into the target div.
gradable_items_inputs.html.erb
GradedItem is the model where the grades for each GradableItem are stored. A GradedItem belongs_to :gradable_item and belongs_to the lesson.
<%= f.simple_fields_for :graded_items do |graded_item| %>
<%= gradable_item.name %>
<%= graded_item.input :gradable_item_id, input_html: { value: gradable_item.id } %>
<% end %>
When I put the above code into the gradable_items_inputs.html.erb file, I get 'undefined local variable "f"' error. I tried moving the f.simple_fields_for call outside of AJAX template file...but, then I get the same error, but for the graded_item variable. One way or another I need to pass that form variable to the template via javascript.
This is an overview of what my code looks like:
parent form do |f|
f.association :lesson
nested_form do |n|
// the fields that go here are dependent on the lesson that is chosen above. A model called LessonGradableItem is a join model that holds what items a particular lesson should be graded against. I generate an input for each item...then save the result in the GradedItem model.
// since these inputs are loaded via AJAX from an external template, the form is being broken...how can I pass the n form variable to that file?
end
end
I tried passing the form variable as a data param and a url param, but it get interpreted in the gradable_items_inputs.html.erb file as a string.

Don't pass the form variable. Construct the nested form separately. So, create a new Object.new in your action, so that the nested form (in a case similar to mine) doesn't need to know the parent form object. To make this work, I also had to set the id: and name: so that the inputs were given the correct structure.

Related

Rails custom select with partial for collection

I've got a Stay which has_many_and_belongs_to :rooms. Rooms are created by admin so they aren't created along with Stays therefore I'd like to be able to select rooms from given collection but I'd like to create custom looking select in my case using simple_fields_for.
The code I have in my form partial in order to achieve what I want:
= f.simple_fields_for :rooms, Room.available do |rf|
= render 'reservations/form/resource', f: rf
My resource partial so far has only one line: = f.hidden_field :id, value: f.object.id
And when I hit refresh I get:
undefined method `id' for #<Room::ActiveRecord_Relation:0x007feb6f3fc2f0>
Did you mean? ids
but when I try ids I only get one hidden input which has this code:
<input value="1 2 3 4 5 6 7 8 9" type="hidden" name="reservation_stay[rooms][id]" id="reservation_stay_rooms_id">
How do I make it create an input for every room?
A common misconception is that fields_for (or the SimpleForm wrapped simple_fields_for) should be used when assigning associations (linking records together) - it is only relevant if you are using accepts_nested_attributes to create or update nested resources in the same form.
When assigning associations from a collection in Rails you pass a _ids (rooms_ids) param with an array.
For example:
Stay.first.update(room_ids: [1, 2, 3])
The _ids setter automatically creates or removes the associations - even if they are indirect.
Thats how the form options helpers in Rails work.
<%= f.collection_select :rooms_ids, Room.available, :id, :room_number %>
The easiest way to get the correct params with a custom UI is to actually use a select and hide it instead of using a hidden input.

Rails Association - Simple Form with separate models and forms in one view

I'm using simple form with my rails 4 app.
I have 3 models in the app. One called Project, one for Scope and one for Data (the model is called datum but the view is called data).
There is a has and belongs to many association between Project and Scope. Data belongs to Scope.
I ask users to outline the scope of the project in general (by asking high level questions in the scope form) and then depending on the answers to the true/false questions in the scope form, I render partials which are forms created in the Data view.
In my scope form I have a question:
<%= f.input :data, :as => :boolean, :label => false, :inline_label => true %>
Those partials from the data view are rendered in the new project view. I have written this line to try to show the data form if the answer to the question about data (asked in the scope form) is true.
In my new project view I have:
<% if #project.scopes.data == true %>
<%= render "data/form" %>
<% end %>
In my schema I have a join table form projects_scopes. I also have a foreign key for scope_id in my data model.
However, I get this error:
`enter code here`undefined method `data' for #<ActiveRecord::Associations::CollectionProxy []>
Does anyone know what I've done wrong?
Thank you
You need to do a nested form. Basically, you end up doing another simple form inside of your simple form for the nested object, then allow your primary object to accept the nested attributes for the nested object. Here is a reference that goes into detail on implementation. I believe you can find it on youtube as well.
http://railscasts.com/episodes/196-nested-model-form-revised?view=comments
Also depending on how complex your form gets, you may look at doing a form object.

Why is my Rails form helper written improperly?

I know I've written it wrong, but I'm looking at the documentation and can't figure out how.
My model is Quote and has three fields, body, attribution, and work, all strings. The form is intended to add a new quote to a page of quotations.
on main/index.html.erb
<%= form_for(:quote, url: {action: 'create'}) do |f| %>
<%= f.text_field :body %>
<%= f.text_field :attribution %>
<%= f.text_field :work %>
<%= submit_tag "Submit" %>
<% end %>
in main_controller.rb
def create
Quote.create(body: params[:body], attribution: params[:attribution], work: params[:work])
end
The form submits, and an entry is saved to the database -- but it's a totally blank entry. I'm not sure why. Help would be appreciated!
Three things:
The way rails forms are supposed to work, you're not meant to get body, attribution, etc independently, they should be wrapped up into a quote object. But...
In your form, your not properly binding an object to the form the way rails expects. You can read more in the documentation here: http://guides.rubyonrails.org/form_helpers.html#binding-a-form-to-an-object. You could also generate a fake scaffold rails generate scaffold HighScore game:string score:integer to generate a fake model and see an example of how it's supposed to work. The default scaffolding even has simple examples of how to deal with save errors.
Finally, as #Paven suggested, when you get confused, be sure to look at what's going on in your log - i.e. what params are being posted to your create action. That is always helpful and a good way to diagnose problems quickly.
Your form does't need the action argument. The form_for helper uses ActiveRecord objects to determine the path, meaning as long as you build your object correctly, you won't need to determine your path individually:
<%= form_for #quote do |f| %>
Secondly, you'll want to look at your create method:
#app/controllers/quotes_controller.rb
def new
#quote = Quote.new
end
def create
#quote = Quote.new(quote_params)
end
private
def quote_params
params.require(:quote).permit(:body, :attribution, :work)
end
The problem is you're not sending an ActiveRecord object to your form_for helper. You can read the explanation here:
In Rails, this is usually achieved by creating the form using form_for
and a number of related helper methods. form_for generates an
appropriate form tag and yields a form builder object that knows the
model the form is about. Input fields are created by calling methods
defined on the form builder, which means they are able to generate the
appropriate names and default values corresponding to the model
attributes, as well as convenient IDs, etc. Conventions in the
generated field names allow controllers to receive form data nicely
structured in params with no effort on your side.
In order to get the form working correctly, you need to be able to provide a valid ActiveRecord object (#variable), which the helper can use to determine the url etc
My code above helps you provide a new ActiveRecord variable, and allows you to use it in the form. This should allow the form_for method to send your data to the create method, which will then create & save an object in the db for you

More than a simple nested form

campaign.rb
class Campaign < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :item
end
item.rb
class Item < ActiveRecord::Base
belongs_to :campaign
end
Campaign has 2 attributes: title and description
Item has 1 attirubte: name
I'll try explain myself by words, I want to create a nested form where they user insert the campaign's name and description but he can insert more than just 1 item, he can insert a list of items (in particular there will be a "+" button that when clicked a new item row will appear and the user can insert items).
At the end all is send all together clicking just one submit button.
How can I reach my goal with rails?
I answered a question just yesterday. Here's the link: Rails accepts_nested_attributes_for with f.fields_for and AJAX
I'll write out how it works & provide some resources to help give you some more ideas:
How It Works
Loading associative fields into a form is done by using f.fields_for
You'll do it like this:
#app/views/campaigns/new.html.erb
<%= form_for #campaign do |f| %>
<%= f.fields_for :items do |a| %>
<%= a.text_field :information %>
<% end %>
<% end %>
In order to get this to work, you have to build the associated ActiveRecord objects in the backend before you render the view, like this:
#app/controllers/campaigns_controller.rb
def new
#campaign = Campaign.new
#campaign.items.build
end
Adding Extra Fields Via Ajax
Adding extra fields with Ajax requires engineering a new solution to the issue
The way you do this is to take the f.fields_for text & put it into a partial. This partial can be called from the original view, as well as another view (which we can render through Ajax)
The Ajax part works by basically taking a request from your form (the ajax request), and then using another action in your controller to build a new ActiveRecord object & render another partial that will contain another form. This partial will then call the original f.fields_for partial, allowing you to render another field
Your Ajax can then extract the new field & append it to your page. The way you get around the id issue (keeping the IDs sequential & unique) is to employ the child_index method, and use Time.now.to_i to generate a timestamp
If you read my answer referenced at the top of this answer, all of this will make sense :)
Some great resources for this:
RailsCasts Nested Forms
Adding Fields With Ajax
A nice gem along with tutorial is available from ryanbates who is the author of railscasts.com site.You can use this and have a look at tutorial here
And also if you want to try manually use the fields_for while writing in the form like here and manage some jquery code for add or remove.

rails dynamic nested form data select

Rails gurus, do you know of a standard solution to this problem I've been struggling with?
In my app, the user can define properties for his objects. So before generating his list of objects (let's say they are books), he can specify which properties he cares about and their potential values, and then for each book he will have to input a legal value for each property. So say I put in for my properties: length (legal values "long", "short") and difficulty ("easy", "hard"). On a different bookshelf, a different list of books could have different properties (cover_color "blue" or "red")
So now I am in my book form. "Add new book on this bookshelf." On the partial, I come up with the list of properties relevant to a new book on this bookshelf (length, and difficulty). Then I look up the legal values for each property. I have a select dropdown in which the user can choose one:
<% for prop in #book.properties %>
<%= prop %> :
<%= f.collection_select :prop_value_select, prop.legal_property_values, :id, :name %>
<%end %>
In my book model, I defined a virtual attribute that will create the "join record" PropertyValue. PropertyValue has legal_property_value_id and book_id.
def prop_value_select=(incoming_id_from_form)
PropertyValue.create!(:legal_property_value_id => incoming_id_from_form, :book=> self)
end
The whole scheme works, except my method is only getting called once, for the first property when the form submits, instead of once for each property.
I am racking my brain... seems very simple, but what's the standard rails way to do something like this?
collect all of the properties into an array and generate the models as a callback?
some magic with a partial for each property?
Thank you!
I think the problem is that you are using a nested model, and all your fields will have the same id's, so rails will only store one nested element.
You should be using fields_for which allows you to handle a nested model.
First you need to specify in your model that it will accept nested attributes. Add the following line
class Book
has_many :properties
accept_nested_attributes_for :properties
end
and in your view you would write something like
<% fields_for :properties do |prop| %>
<%= prop %> :
<%= f.collection_select ...
<% end %>
Hope this helps.

Resources