So, imagine I have this model:
class Car
has_one :engine
end
and the engine model:
class Engine
belongs_to :car
end
When I present the form for the user, so that he can create a new car, I only want to allow him to select from one of the available engines ( the available engines will be in a select
, populated by the collection_select
). The thing is, if I build the field like this:
<%= f.collection_select :engine,Engine.all,:id,:name %>
When I will try to save it, I will get an AssociationTypeMismatch saying that it expected an Engine, but it received a string.
Is this the way to do it?
def create
car = Car.new(params[:car])
engine = Engine.find(params[:engine])
car.engine = engine
if car.save
# redirect somewhere
else
# do something with the errors
end
end
I always felt that stuff, like associating an engine to a car, are done automatically by Rails, but I don't know how to make him do it.
Is switching the has_one
and belongs_to
associations the only way t开发者_运维问答o achieve this?
I am lost and I feel like I'm missing something very basic here.
You should use engine_id
<%= f.collection_select :engine_id, Engine.all, :id, :name %>
UPD
as far as Engine
is not belongs_to Car
so you shoulduse Nested Attributes here. This screencast will be very useful for you:
- http://railscasts.com/episodes/196-nested-model-form-part-1
- http://railscasts.com/episodes/197-nested-model-form-part-2
Checkout api: http://apidock.com/rails/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for
Short intro:
class Car
has_one :engine
accepts_nested_attributes_for :engine
end
and in your form:
<%= form_for @car ... do |f| %>
...
<%= f.fields_for :engine do |b| %>
<%= b.collection_select :id, Engine.all, :id, :name %>
...
<% end %>
...
<% end %>
I faced the same problem and here is my solution (improvements based on the fl00r answer):
Do everything as fl00r have said.
Add
optional: true
to the class description
class Engine belongs_to :car, optional: true end
More info at: http://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html
- Modify
<%= f.fields_for :engine do |b| %> <%= b.collection_select :id, Engine.all, :id, :name %> ... <% end %>
to
<%= f.fields_for :engine, :include_id => false do |b| %>
<%= b.collection_select :id, Engine.all, :id, :name %>
...
<% end %>
More info at: Stop rails from generating hidden field for fields_for method
- Modify your EngineController
3.1. Modify
def car_params
params.require(:car).permit(:name)
end
to
def car_params
params.require(:car).permit(:name, engine_attributes: [:id, :name])
end
More info at: Rails 4 Nested Attributes Unpermitted Parameters
3.1. Add a function (private)
def set_engine
engine_id = car_params[:engine_attributes][:id]
@engine = Engine.find(engine_id)
@car.engine = @engine
@car.save
end
3.2. Modify EngineController#update
def update
respond_to do |format|
if @car.update(car_params)
format.html { redirect_to @car, notice: 'Car was successfully updated.' }
format.json { render :show, status: :ok, location: @car }
else
format.html { render :edit }
format.json { render json: @car.errors, status: :unprocessable_entity }
end
end
end
to
def update
respond_to do |format|
if set_engine && @car.update(car_params)
format.html { redirect_to @car, notice: 'Car was successfully updated.' }
format.json { render :show, status: :ok, location: @car }
else
format.html { render :edit }
format.json { render json: @car.errors, status: :unprocessable_entity }
end
end
end
Response to previous answer from Kyle--I like this simple approach however I had to change it as follows to work for my app (I'm in Rails 3.2.13). The main point was that the map needs to return an Array--so I think it was perhaps a typo putting two arrays separated by a comma. I also changed the string reference to the id attribute to a symbol but I'm not sure if this is essential or just another option. At an rate, here is what worked for me.
<%= f.select :class_location_id, ClassLocation.all.map {|e| [e.place, e.id]} %>
精彩评论