I've been trying to work through this for a few days and can't get anything to work. I have been building my first app based on Michael Hartl's amazing tutorial: http://ruby.railstutorial.org/. Additionally, I have tried this tutorial, but the differences in my code and his prove to be too great for me to follow along.
Where my app differs from Michael Hurtl's is that I am trying to create a site where you can post your left over cans of paint (instead of microposts, AKA twitter). When I created the app, I had a column in the Paints model called "color_family". Now I am looking to change it from a text field to a drop down with predetermined values, e.g. "Reds", Oranges", "Yellows", Greens" etc.
I started out by generating a new scaffold:
rails generate scaffold Color_Family family:string
then I generated a migration:
rails generate migration AddColor_FamilyToPaints family_id:int
and migrated it all.
Then I created the associations
class ColorFamily < ActiveRecord::Base
has_many :paints
end
and
class Paint < ActiveRecord::Base
attr_accessible :family_id, :name, :hex, :location, :quantity, :additional_info
belongs_to :user
belongs_to :color_family
...
end
This is where I get lost, and any tutorial I try to follow breaks everything. Where do I define my predetermined list of color_families?
Is it even worth it for me to go through the creation of a new model? I previously tried this in the form field:
<%= form_for(#paint) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.label :color_family %>
<%= select_tag(:color_family, options_for_select([['Red', 1],
['Orange', 2],
['Yellow', 3],
['Green', 4],
['Blue', 5],
['Purple', 6],
['Black', 7],
['Grey', 8],
['White', 9],
['Cream', 10],
['Brown', 12]])) %>
and while it created a dropdown for me, it never captured the info when I added a new paint.
Any help is greatly appreciated. Also, a link to a tutorial would probably do me the biggest help as I've very new to RoR and backend stuff in general.
I'm not sure if you are doing the reading version of the book, or the video. Personally, I recommend both! Absolutely amazing tutorial! One of the first things he does mention though, "Scaffold is not really for the real world" and you should consider this. When I'm doing projects, new old or just refactoring, I usually add everything by hand with the script/generate. The only "scaffold" I've ever used was the scaffold_controller because I was too lazy to do the controller by hand.
The short answer, you should have another model "Color" and the form should:
f.collection_select(:color_id, Color.find(:all), :id, :name, {:include_blank => 'Please Select A Color'})
And the ColorFamily should probably be a has_many_and_belongs_to_many Colors
If you could give me a run down of details associations supposed to be taking place, I can write up a small data modal for you.
Edit #1
You are needing a has_one :through relationship. The general concept will be...
Pivot tabel:
rails g migration ColorFamilyPaints paint_id:integer color_family_id:integer
Paint Class:
class Paint < ActiveRecord::Base
attr_accessible :family_id, :name, :hex, :location, :quantity, :additional_info,
:color_families_attributes # Need to add this in order for you to be able to post with drop down
belongs_to :user
...
# Creates the Relationship
has_one :color_families, :through => :color_family_paints
# Allows color families to be nested in the form.
accepts_nested_attributes_for :color_families, :allow_destroy => true
end
You'll notice a few changes. Addition to the attr_accessible, and the accepts_nested_attributes_for (you may need this, not sure with a has_one though). When you build the form, look at the ID/Name of the select box. If it ends in _attributes, use the accepts line. If not, you don't need it. Alter the :color_families_attributes to match the name of the select box.
Form HTML:
<%= form_for(#paint) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.label :color_family %>
<%= f.collection_select(:color_family, ColorFamily.find(:all), :id, :name, {:include_blank => 'Please Select Color Family'}) %>
</div>
<% end %>
More information on associations # RoR website.
Related
I am using collection_check_boxes to create object in a has_many through relation;
here some models:
#emotion.rb
class Emotion < ActiveRecord::Base
has_many :emotional_states
has_many :reports, :through => :emotional_states
end
#report.rb
class Report < ActiveRecord::Base
has_many :emotional_states
has_many :emotions, :through => :emotional_states
[... other "irrelevant" stuff here ...]
end
#emotional_states.rb
class EmotionalState < ActiveRecord::Base
belongs_to :report
belongs_to :emotion
end
As you may understand when I create a Report I also select with a collection_check_box a list of Emotions I want to bind to that report (through the model EmotionalState); Everything works on create (I retrieve the hash values and if #report.save I also create EmotionalStates with the #report.id and #emotion.id.)
But when it cames to edit the Report I would like to edit also the associated EmotionalStates (this means creating new EmotionalStates or deleting old one).
How can I populate the select_check_boxes with ALL the available Emotions having checked that emotions that are alredy associated through the EmotionalStates bojects?
If I write something like:
<%= collection_check_boxes(:report, :emotion_id, #report.emotional_states.map{|e| e.emotion}, :id, :name) %>
I'll get a unchecked checkbox for every alredy associated Emotion.
<%= collection_check_boxes(:report, :emotion_id, Emotion.all, :id, :name, :checked => #report.emotional_states.map{|e| e.emotion}) %>
While this code will correctly returns Emotion.all, but will not check the emotions alredy associated to #report through #report.emotional_states.
I've searched all around the wheb for examples on the usage of :checked options for collection_select_boxes without any results...
any hint?
I did the same once in this way.you can also try :
Emotions:
<% Emotion.all.each do |emotion| %>
<%= check_box_tag 'report[emotion_ids][]' , emotion.id, #report.emotion.include?(emotion) %><%= emotion.name %><br/>
<% end %>
In Controller add :emotion_ids=>[] into strong parameters.
And one line into controller update method:
params[:report][:emotion_ids] ||= []
After coming back to this bug I discovered that the problem was an incorrect use of the .map method, mapping a whole object (e.emotion) instead its id (e.emotion.id).
This easily fixed my problem:
<%= collection_check_boxes(:report, :emotion_id, Emotion.all, :id, :name, :checked => #report.emotional_states.map{|e| e.emotion.id}) %>
Thank you for your help!
First question: how to validate a model relation and mark it in form when validation failed.
I have a subject model:
class Subject < ActiveRecord::Base
belongs_to :semester
validates_presence_of :semester
end
In my view (form):
<%= select_tag :semester, options_from_collection_for_select(#semesters,"id","name") %>
The validates_presence_of works fine. But when the validation fails (user forgot to enter semester ). The semester input is not marked in red.
Second question: how to validate an input field.
In my view, I also have a university input field, but model subject has no relationship with university, no field university in subject table. So how to validate it and mark it in red.
Thanks in advance.
If you want to get the fields with error displayed in red "out of the box", you must use a form builder. The code will looks like f.select ... instead of using select_tag.
With the form builder, the fields with errors are created inside a <div class="field_with_errors">...</div>. The css file generated by scaffolding displays these fields in red; if you're not using it, you must add the css rules to your css.
# app/models/subject.rb
class Subject < ActiveRecord::Base
belongs_to :semester
validates :semester, :university, presence: true # new syntax http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates
end
# app/views/subjects/_form.html.erb
<%= form_for #subject do |f| %>
Semestr: <%= f.collection_select :semester_id, Semestr.all, :id, :name, prompt: true %>
University: <%= f.text_field :univercity %>
<% end %>
For more information about building forms in rails (with validations enabled) you could find there http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html
Those "red errors" that you hope for are probably coming from a form helper gem like formtastic, feel free to check that out.
I have literally no idea what your second question is asking, but if you're looking for a custom validation. Check out the rails docs on them for help.
If you'd like more help, (please) edit your question to be more clear (thanks)
Got ActiveRecord::AssociationTypeMismatchComponent(#70354292616060) expected, got String(#70354278277000) when create object "Machine" below;
Objective: Try to assign "attributes" to a model that is referred to the other model.
Question: I would like to hear from expert that what is the best practice for this kind of operation.
Settings:
Rails 3.2.12 / Ruby 1.9.3-p194:
Models:
class Machine < ActiveRecord::Base
attr_accessible :cpu, :name, :ram
belongs_to :cpu, class_name: "Component", foreign_key: "component_id"
belongs_to :ram, class_name: "Component", foreign_key: "component_id"
end
class Component < ActiveRecord::Base
attr_accessible :name
has_many :machines
end
Views for Machine:
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :cpu %><br />
<%= f.collection_select :cpu, Component.all, :id, :name %>
</div>
<div class="field">
<%= f.label :ram %><br />
<%= f.collection_select :ram, Component.all, :id, :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
Procedure:
Create few objects to Component such that id:1 name:"cpu1" & id:2 name:"ram1"
Go to machines_path and create Machine with selecting cpu1 and ram1 from pull down
Got following error when submit Component(#70354292616060) expected, got String(#70354278277000)
I tried to change :cpu and :ram in View with :cpu_id and :ram_id. Thereafter, I can create the model without error. However I cannot access cpu & ram directly from machine;
1.9.3-p194 :001 > Machine.first
Machine Load (0.1ms) SELECT "machines".* FROM "machines" LIMIT 1
=> #<Machine id: 2, name: "test", cpu_id: 1, ram_id: 1, created_at: "2013-05-06 16:42:47", updated_at: "2013-05-06 16:42:47">
1.9.3-p194 :002 > Machine.first.cpu
Machine Load (0.2ms) SELECT "machines".* FROM "machines" LIMIT 1
=> nil
1.9.3-p194 :003 > Machine.first.ram
Machine Load (0.2ms) SELECT "machines".* FROM "machines" LIMIT 1
=> nil
So I had to create following model methods
class Machine < ActiveRecord::Base
(snip)
def cpu
Component.find_by_id(self.cpu_id)
end
def ram
Component.find_by_id(self.ram_id)
end
Then I can get expected output
1.9.3-p194 :002 > Machine.first.cpu.name
Machine Load (0.2ms) SELECT "machines".* FROM "machines" LIMIT 1
Component Load (0.2ms) SELECT "components".* FROM "components" WHERE "components"."id" = 1 LIMIT 1
=> "CPU1"
But I feel it's redundant and looking for a simpler way to do this.
Appreciate if any suggestions.
First off, having your select boxes work with cpu_id and ram_id is the right thing to do. If you don't then rails will (simplifying slightly) do
machine.cpu = params[:machine][:cpu]
Params are always strings, so this is trying to assign a string to the association, hence the error about it expecting a component but getting a string.
Your second problem is in how you've declared your associations: you've set both ram and cpu associations to use the component_id column to store the id of the corresponding column, which clearly can't work. Instead you probably want the CPU association to use the cpu_id column and the ram association the ram_id column. This is actually the default - remove the foreign key option you're passing to those calls to belongs to and you should be ok.
You associations on component would need a foreign key option though. The simplest thing would be to have one association for the machines that the component acts as CPU for and another for the ram. You might consider setting up single table inheritance, in which case the CPU subclass would only have the association relating to cpu_id and the ram subclass would only have the association relating to ram_id
So, if i'm reading this right, you need to be able to set fields to the other model from within your form.
That's nested attributes.
do a form_for #components within the form_for #machines and give attr_accessible permissions to those fields from the Machine model
This is helpful http://railscasts.com/episodes/196-nested-model-form-part-1
I am creating a scaffold -
rails g scaffold Contact email:string email_provider:string
but I want the email provider to be a drop down (with gmail/yahoo/msn as options) and not a text field. How can I do this ?
You can take a look at the Rails documentation . Anyways , in your form :
<%= f.collection_select :provider_id, Provider.order(:name),:id,:name, include_blank: true %>
As you can guess , you should predefine email-providers in another model -Provider , to have where to select them from .
Or for custom options
<%= f.select :desired_attribute, ['option1', 'option2']%>
You create the collection in the Contact controller -
app/controllers/contacts_controller.erb
Adding
#providers = Provider.all.by_name
to the new, create and edit methods, using a scope for the by_name in the Provider model - app/models/provider.rb - for the ordering by name
scope by_name order(:name)
Then in the view - app/views/contacts/_form.html.erb - you use
<%= f.collection_select :provider_id, #providers, :id, :name, include_blank: true %>
For rails forms, I also strongly recommend you look at a form builder like simple_form - https://github.com/plataformatec/simple_form - which will do all the heavy lifting.
This is a long way round, but if you have not yet implemented then you can originally create your models this way. The method below describes altering an existing database.
1) Create a new model for the email providers:
$ rails g model provider name
2) This will create your model with a name string and timestamps. It also creates the migration which we need to add to the schema with:
$ rake db:migrate
3) Add a migration to add the providers ID into the Contact:
$ rails g migration AddProviderRefToContacts provider:references
4) Go over the migration file to check it look OK, and migrate that too:
$ rake db:migrate
5) Okay, now we have a provider_id, we no longer need the original email_provider string:
$ rails g migration RemoveEmailProviderFromContacts
6) Inside the migration file, add the change which will look something like:
class RemoveEmailProviderFromContacts < ActiveRecord::Migration
def change
remove_column :contacts, :email_provider
end
end
7) Once that is done, migrate the change:
$ rake db:migrate
8) Let's take this moment to update our models:
Contact: belongs_to :provider
Provider: has_many :contacts
9) Then, we set up the drop down logic in the _form.html.erb partial in the views:
<div class="field">
<%= f.label :provider %><br>
<%= f.collection_select :provider_id, Provider.all, :id, :name %>
</div>
10) Finally, we need to add the provders themselves. One way top do that would be to use the seed file:
Provider.destroy_all
gmail = Provider.create!(name: "gmail")
yahoo = Provider.create!(name: "yahoo")
msn = Provider.create!(name: "msn")
$ rake db:seed
<%= f.select :email_provider, ["gmail","yahoo","msn"]%>
Please have a look here
Either you can use rails tag Or use plain HTML tags
Rails tag
<%= select("Contact", "email_provider", Contact::PROVIDERS, {:include_blank => true}) %>
*above line of code would become HTML code(HTML Tag), find it below *
HTML tag
<select name="Contact[email_provider]">
<option></option>
<option>yahoo</option>
<option>gmail</option>
<option>msn</option>
</select>
Rails drop down using has_many association for article and category:
has_many :articles
belongs_to :category
<%= form.select :category_id,Category.all.pluck(:name,:id),{prompt:'select'},{class: "form-control"}%>
In your model,
class Contact
self.email_providers = %w[Gmail Yahoo MSN]
validates :email_provider, :inclusion => email_providers
end
In your form,
<%= f.select :email_provider,
options_for_select(Contact.email_providers, #contact.email_provider) %>
the second arg of the options_for_select will have any current email_provider selected.
I wanted to display one thing (human readable) but store another (an integer id).
Small example
Here's a small example that helped:
<%= form.select(:attribute_name, {cat: 5, dog: 3} )%>
The {cat: 5, dog: 3} will display "cat" and "dog", but save 5 and 3.
Real world example
Here's the actual use case. It displays the names of sellers (that humans can read), but saves the sellers' id (an integer):
<div class="field">
<%= form.label :seller_id %>
<%= form.select :seller_id, seller_names_and_ids(), {include_blank: true}, {required: true, class: "form-control"} %>
</div>
And the helper is defined as:
def seller_names_and_ids
# We want this to produce a hash of keys (the thing to display) and values (the thing to save,
# in thise case the seller_id integer)
sellers = Seller.all
h = {}
sellers.each do |seller|
thing_to_display = seller.name + " (" + seller.id.to_s + ")"
thing_to_save_in_db = seller.id
h.store(thing_to_display, thing_to_save_in_db)
end
h
end
Simply, a Contact can have various associated Time Windows, which may or may not be Active as a Schedule. To wit:
Models
class Contact < ActiveRecord::Base
has_many :schedules
has_many :time_windows, :through => :schedules
accepts_nested_attributes_for :schedules, :allow_destroy => true
end
class TimeWindow < ActiveRecord::Base
has_many :schedules
has_many :contacts, :through => :schedules
end
class Schedule < ActiveRecord::Base
belongs_to :contact
belongs_to :time_window
end
View
<% TimeWindow.all.each do |tw| %>
<% schedule = Schedule.find_by_contact_id_and_time_window_id(#contact.id, tw.id)
schedule ||= Schedule.new %>
<p>
<%= f.label tw.description %>
<%= hidden_field_tag "contact[schedules_attributes][][id]", schedule.id %>
<%= check_box_tag "contact[schedules_attributes][][time_window_id]",
tw.id, #contact.time_windows.include?(tw) %>
<%= check_box_tag "contact[schedules_attributes][][active]", nil,
schedule.active %>
</p>
<% end %>
This submits something like this:
Parameters: { "commit" => "Update", "contact" => {
"group_ids" => ["2"], "enabled" => "1",
"schedules_attributes" => [ { "time_window_id"=>"1", "id"=>"46"},
{ "time_window_id" => "2", "id" => "42", "active" => "on" },
{ "time_window_id" => "3", "id" => "43"},
{ "time_window_id" => "4", "id" => "44", "active" => "on"}],
"last_name" => ...
The update action in the controller is basically stock, except to handle another instance of another related model which I coded using the "Handling Multiple Models" example from the Advanced Rails Recipes book.
According to this API doc, I think the above ought to work. However, nothing about the Schedules is getting updated. This shows up in the server log:
[4;35;1mSchedule Update (0.2ms)[0m [0mUPDATE `schedules` SET `updated_at` = '2010-09-30 20:39:49', `active` = 0 WHERE `id` = 42[0m
[4;36;1mSchedule Update (0.1ms)[0m [0;1mUPDATE `schedules` SET `updated_at` = '2010-09-30 20:39:49', `active` = 0 WHERE `id` = 44[0m
(NetBeans is giving me those stupid "[0m"'s in the output. I don't know what's wrong there.)
The SQL shows that the "active" boolean field is getting set to 0 where checked. How do I get this to correctly set the active bit?
As a followup, how would I organize this to get rid of the Schedule "connection" at all? I'm thinking I need to submit a :_delete with the Schedule from the form, but how would I do that conditionally when a checkbox is involved?
Thanks for any help you can provide. Rails is turning out to be a vast subject for me, and I want to do it "right." I'm really close here, but there's got to be a way to make this -- not just correct -- but elegant. The view code just feels way too cumbersome to be proper Rails. ;-)
I've kept trying different approaches to this problem, and I've come up with this, which works. Mostly. The only problem is that it doesn't handle NOT having a "Schedule" for each "Time Window". The form will render, and I'll get a disabled check_box (to prevent me from trying to delete something that isn't there), but I don't have a way to add it back, and submitting without it throws off the params hash (and causes Rails to give me an "Expected Hash (got Array)" error)
<% TimeWindow.all.each do |tw| %>
<% schedule = Schedule.find_by_contact_id_and_time_window_id(#contact.id, tw.id)
schedule ||= Schedule.new %>
<% f.fields_for "schedules_attributes[]", schedule do |sf| %>
<p>
<%= sf.label tw.description %>
<%= sf.hidden_field :id %>
<%= sf.check_box :_destroy, :disabled => schedule.new_record? %>
<%= sf.check_box :active %>
</p>
<% end %>
<% end %>
Note that the "schedules_attributes[]" array will automatically give you an existing ID within the braces in your HTML (which is nice), but the _attributes hash is expecting an "id" alongside the other attributes in order to make sense of the sub-hashes.
One of the big lessons I've learned here is that the "check_box_tag" method doesn't (seem to) give me a paired-up hidden field for Rails to parse in the unchecked case. I would have expected this. Adding one in by hand made a mess, which led me to finally giving into the "fields_for" method, and trying many incarnations before finding the appropriate syntax to get what I wanted out of it.
I've realized that my model isn't quite appropriate in this setup, so I'm going to change it, but I was so close to this answer, I wanted to at least get to the point of being able to see the end before I moved on.