Rails link models in a form DRY - ruby-on-rails

I am trying to link multiple rails models in my app. I am trying to let users create reviews on a product using a form. I am trying to use the rails DRY principle.
I first made a bat table with bat name, model year, and an image. Then I created a manufacturer table which only lists the names of the manufacturers of bats. My bats model belongs_to :manufacturer and my manufacturer model has_many :bats.
Instead of creating multiple tables using manufacturer, (listing the name of the manufacturer at least 3 times for each bat) how can I link my two models together?
My form is submitted to the review model. In the form I already have <%= f.collection_select :bat_id, Manufacturer.all, :id, :manufacturer, include_blank: true %>, which lists all of the possible manufacturers in a drop down menu. HOWEVER, nothing is submitted to the :bat_id parameter in the review form when submitted.
--One guess is to have the manufacturer_id integer stored in the bat model as an integer under the column manufacturer_id(Note: already done this, but I don't know how to submit that in a form?)
--Another guess is to have the bat model inherit from the manufacturer model
Any help is greatly appreciated
My full form:
<%= form_for(#review) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field" align= "center">
<h3>Select bat</h3>
<%= f.collection_select :bat_id, Manufacturer.all, :id, :manufacturer, include_blank: true %>
<h3>What do you like about this bat?</h3>
<%= f.text_area :pros, placeholder: "Enter what you like..." %>
<h3>What do you not like about this bat?</h3>
<%= f.text_area :cons, placeholder: "Enter what you don't like..." %></br>
</div>
<div align="center">
<%= f.submit "Add Review", class: "btn btn-large btn-info" %>
</div>
<% end %>

according to the documentation
http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_select
the params in the collection_select is supposed to be:
f.collection_select(:post, :author_id, Author.all, :id,
:name_with_initial, prompt: true)
where post is the Model,author_id is the attribute
or you could try using:
=f.select( :bat_id, options_from_collection_for_select(#manufacturers,"id","manufacturer",
f.object. bat_id),{})
and put the
#manufacturers = Manufacturer.all
inside the controller

Related

How to use nested forms in Rails if the fields have the same name?

I have two models, Dog and Owner, and I want their names to be the same, and would be redundant if I asked to fill out the fields twice (once for the dog and another time for the owner). I'm wondering if there's a simpler way to update the two databases with one input.
<h1>Create a new Dog:</h1>
<%= form_for(#dog) do |f|%>
<div>
<%= f.label :name%>
<%= f.text_field :name%>
</div><br>
<div>
<%= f.label :breed%>
<%= f.text_field :breed%>
</div><br>
<div>
<%= f.label :age%>
<%= f.text_field :age%>
</div><br>
<div>
<h3>create a new owner:</h3>
<%= f.fields_for :owner, Owner.new do |owner_attributes|%>
<%= owner_attributes.label :name, "Owner Name:" %>
<%= owner_attributes.text_field :name %>
<% end %>
</div>
<%= f.submit %>
<% end %>
First of all, not sure why you want to keep the name of the owner and the dog same.
However, there can be many ways to achieve what you want:
You can simply omit the owner name from the form.
So you no longer need: <%= owner_attributes.label :name, "Owner Name:" %>
OR you no longer need:
<div>
<%= f.label :name%>
<%= f.text_field :name%>
</div><br>
And in the Owner/Dog model, you can pass the name of the dog/owner in a callback - maybe after_initialize or before_save or before_validation depending on your validation requirements.
class Dog
belongs_to :owner
before_validation :set_name
private
def set_name
self.name = owner&.name
end
end
You can make the owner name as a hidden field instead and can write some javascript to update the hidden field with the dog name before submitting the form or onblur event. I would prefer the first approach since it's simpler and more secure than only JS solution to maintain database consistency
If dogs belongs_to and owner, you don't really need to store the owner's name separately. You can just call dog.owner.name anywhere you have a Dog instance. Having said that, it is relatively straightforward to append attributes on top of the POSTed form values in your controller using .merge():
def create
#dog = Dog.new(dog_params.merge(owner: params[:dog][:owner])[:name])
if #dog.save
...
end
end
def dog_params
params.require(:dog).permit(:name, :breed, :age, owner: %i[name])
end

Rails: Automatically assign parameters in a form

I'm creating a Rails app which allows users to create different animals and assign them attributes.
One of the attributes they an assign is "range". There are currently four types of ranges and each range has a different id:
land - id:1
sea - id:2
air - id:3
underground - id:4
Range has its own controller, model, and view in my app.
Each animal can be created with an id, a name, and a range. But I also have a column entitled range_id, which is what I am using to tie the animal back to the range.
{name:"cat", range:"land"}
#ids are assigned automatically
For views/animals/new.html.erb, I include a form that asks for each animal's attributes.
<h1> Add Animal </h1>
<%= form_for :animal, url: animals_path do |f| %>
<p>
<%= f.label :name %> <br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :range %> <br>
<%= f.select :range, ['land', 'sea', 'air', 'underground'], :prompt => 'Select One' %>
</p>
<p>
<%= f.submit %>
<p>
<% end %>
My problem is, I need to automatically assign a range_id to each new Animal based on the range. So, if a user created "whale" with a range "sea", that Animal also needs to have a range_id of 2.
How can I do this automatic assignment? Here is my current animal_params method in animals_controller.rb:
private def animal_params
params.require(:animal).permit(:id, :name, :range, :range_id)
end
Should range_id be included there, or what would be the easiest way to alter it automatically?
You can find an answer in the documentation here - you shouldn't be hardcoding these values into your view, and should instead be using ActiveRecord to populate the values for you.
<%= f.select :range_id, options_for_select(Range.all.collect { |range| [range.name, range.id]}, f.object.range_id), {}, class: 'form-control', include_blank: 'Please Select' %>
You can get the reference from here.
I assume that you have a model named Range which has two attributes - name, id. The select box above will show all the ranges in the dropdown.

Serialized fields_for not populating from db on edit

On the edit page for this form all of the fields outside of the fields_for tag (inbox name, automatic reconciliation, and a few others not listed here) are all populating based on their corresponding db value. However, everything inside the fields_for tag are not, even though they're posting to the db just fine.
I posted :group_member_roles as an example but there are a few other fields inside their own other fields_for that are doing the same thing. It's just confusing that it will post to the db but not display on edit.
The more I read into fields_for the more I feel like I'm not using it correctly. It seems to be more inclined to populating db tables outside of the one your form is currently referencing, but I'm just trying to serialize data within the inbox table. When I look at the :group_member_roles column I want it to be an array/hash containing process true/false, action add/delete, and a string of values.
#_form.html.erb
<%= form_for(#inbox) do |f| %>
<%= f.label :inbox_name %>
<%= f.text_field :name, placeholder: "Inbox Name" %>
<%= f.label :automatic_reconciliation, "Turn on/off automatic reconciliation" %>
<div class="switch small">
<%= f.check_box :automatic_reconciliation, class: "switch-input" %>
<label class="switch-paddle" for="inbox_automatic_reconciliation">
<span class="show-for-sr">Automatic reconciliation</span>
<span class="switch-active" aria-hidden="true">On</span>
<span class="switch-inactive" aria-hidden="true">Off</span>
</label>
</div>
<%= f.fields_for :group_member_roles do |group_member_roles| %>
<h4>Group Member Roles</h4>
<%= group_member_roles.label :process, "Turn On/Off Processing" %>
<div class="switch small">
<%= group_member_roles.check_box :process, class: "switch-input" %>
<label class="switch-paddle" for="inbox_group_member_roles_process">
<span class="show-for-sr">Group Member Roles Processing</span>
<span class="switch-active" aria-hidden="true">On</span>
<span class="switch-inactive" aria-hidden="true">Off</span>
</label>
</div>
<%= group_member_roles.label :action, class: "hide" %>
<%= group_member_roles.select :action, ["Add", "Delete"], { selected: "Add" }, { class: "hide" } %>
<%= group_member_roles.label :values %>
<%= group_member_roles.text_field :values, placeholder: "1234, 1337, 1986" %>
<% end %>
Thanks in advance for any help or guidance.
The fields were being stored as a hash and the field was looking for an object to populate with so I added an OpenStruct dummy object to the fields_for to make it so. If anyone can think of a better way please let me know as this is pretty ugly code.
<%= f.fields_for :group_member_roles, OpenStruct.new(f.object.group_member_roles) do |group_member_roles| %>

Rails Multiple Input Field in Form to One Integer Attribute in Model

I am trying to allow a user to input two different things in two different drop down menus from the same form and it will store an integer into a review table.
I want the user to be able to select model_name in one drop down and manufacturer in another drop down. The result will store a bat_id integer into the form. (Telling you which bat the user is selecting)
I have seen a couple questions about date & time but they store the values directly in the model. I am trying to store an integer - bat_id so that the bat_id will directly link the review model to the bat model.
Examples I have found that are close:
How do ruby on rails multi parameter attributes really work (datetime_select)
Rails multiple fields to one model attribute
Using multiple input fields for one attribute
Rails Update Single Attribute with Multiple Fields
My form now:
<%= form_for(#review) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field" align= "center">
<h3>Select Brand</h3>
<%= f.collection_select :manufacturer_id, Manufacturer.all, :id, :manufacturer, include_blank: true %>
<h3>Select Bat</h3>
<%= f.grouped_collection_select :bat_id, Manufacturer.all, :bats, :manufacturer, :id, :model_year_and_name, include_blank: true %>
<h3>What do you like about this bat?</h3>
<%= f.text_area :pros, placeholder: "Enter what you like..." %>
<h3>What do you not like about this bat?</h3>
<%= f.text_area :cons, placeholder: "Enter what you don't like..." %></br>
</div>
<div align="center">
<%= f.submit "Add Review", class: "btn btn-large btn-info" %>
</div>
<% end %>
I am submitting to the review table and trying to submit both of these to the bat_id attribute.
<h3>Select Brand</h3>
<%= f.collection_select :manufacturer_id, Manufacturer.all, :id, :manufacturer, include_blank: true %>
<h3>Select Bat</h3>
<%= f.grouped_collection_select :bat_id, Manufacturer.all, :bats, :manufacturer, :id, :model_year_and_name, include_blank: true %>
In my bat model I have: has_many :reviews & In my reviews model I have: belongs_to :bat
UPDATE: Is it possible to use a hidden field with the combination of javascript and my two inputs to determine my one output bat_id?
Update I changed my dropdown code to what works so that I enter in manufacturer_id & bat_id when both are selected. However I still think there is a way to store one value in my review model. I am using javascript very similiar to this
From a UI perspective this seems broken... users will be able to associate any model year & name with any manufacturer, even if that manufacturer did not produce that model year & name.
Assuming you will introduce some javascript to handle that, from a rails perspective you will get undefined behavior with two :bat_id fields in the same form. I think you need this:
<h3>Select Brand</h3>
<%= f.collection_select :manufacturer_id, Manufacturer.all, :id, :manufacturer, include_blank: true %>
<h3>Select Bat</h3>
<%= f.collection_select :bat_id, Bat.all, :id, :model_year_and_name, include_blank: true %>
Alternatively you can just create one dropdown containing a composite field, like this:
<h3>Select Bat</h3>
<%= f.collection_select :bat_id, Bat.all.sort {|a, b| a.manufacturer_model_year_and_name <=> b.manufacturer_model_year_and_name}, :id, :manufacturer_model_year_and_name, include_blank: true %>
and then in your Bat model introduce something like this:
def manufacturer_model_year_and_name
"#{self.manufacturer.name}: #{self.model_year_and_name}"
end
As discussed in your other answer, you shouldn't need to store the manufacturer_id on your review model.
I would recommend creating a Manufacturer select that isn't accessed in your Review model, but is simply used to filter the list of bats on the form.
The best way to do this is probably to add some custom data attributes to the Bat select.
<%= collection_select :manufacturer, :manufacturer_id, Manufacturer.all, :id, :manufacturer %>
<%= f.select :bat_id, Bat.all.map{ |b| [b.model_year_and_name, b.id, {'data-manufacturer' => b.manufacturer_id}] } %>
Then use some javascript to filter the Bat select when the Manufacturer select is changed.
Unfortunately you cannot just set display: none to an option element to hide it. This does not hide the option in many browsers. So the best method is to use a bit of jQuery to clone the original select every time the manufacturer select is changed, and remove any option that isn't associated with the selected manufacturer. Like so:
// rename the original select and hide it
$('#bat_id').attr('id', 'bat_id_original').hide();
$('#manufacturer_id').on('change', function() {
$('#bat_id').remove(); // remove any bat_id selects
$bat = $('#bat_id_original')
.clone() // clone the original
.attr('id', 'bat_id') // change the ID to the proper id
.insertAfter('#bat_id_original') // place it
.show() // show it
.find(':not(option[data-manufacturer="' + $(this).val() + '"])')
.remove(); // find all options by other manufacturers and remove them
});
You might need to change a few things to get this to work in your installation, but you can view a static demo on jsFiddle here: http://jsfiddle.net/JL6M5/
You will probably need to reject the manufacturer_id field on form submit, avitevet already pointed out this answer which should help there: Rails: Ignoring non-existant attributes passed to create()

Passing extra params through text_field_tag in form_for

I'm in trouble with a form I'm doing on rails 3.
What I want to do is:
I want to pass an extra parameter only to decide how many builds you want to do
to the next form. I'm trying to pass the value through a text_field_tag, but I can't get it
on the controller side.
This is what I've done:
Model:
class Story < ActiveRecord::Base
attr_accessible :resume,
:title,
:prelude,
:chapter_numbers
attr_accessor :chapter_numbers
# etc etc etc
end
View:
<%= simple_form_for(#story) do |f| %>
<div class="field">
<%= f.input :prelude, as: :text, input_html: { rows: 10, style: 'width: 100%' } %>
</div>
<div class="field">
<%= text_field_tag :chapter_numbers %>
</div>
<% end %>
the extra parameter is :chapter_numbers, which I want to catch in the controller as
params[:chapter_numbers], but it's not working. Tried to add it as virtual attribute (don't know if it's necessary)
Thanks in advance!
text_field_tag is an independent field, and won't be sent in your params
text_field_tag And text_field Are Different
You'd need to use f.text_field because this will send the required params to your controller, like this:
<%= f.text_field :chapter_numbers %>
or in your case (with simple form):
<%= f.input :chapter_numbers, as: :text %>
Good resource here about this

Resources