Sorry if this question seems simple, I am very very new to Rails (just started learning a few days ago), but after consulting Google and "Agile Web Development with Rails" I can't find the answer.
I have an issue with Rails 2.3.8 creating a foreign key on two models. My tables look like this:
cars manufacturer
---- ------------
car_make name
car_model country
car_class logo_url
image_url (and default 'id' created by Rails)
manufacturer_id
(and default 'id' created by Rails)
My 'car_make' and 'name' fields are essentially the same; every Car I create, I want to be able to associate it with an existing Manufacturer. This is the column I am trying to create FK on.
My car.rb has 'belongs_to :manufacturer', and my manufacturer.rb has 'has_many :cars' to establish a one manufacturer to many cars relationship. However, when I create a new car (via scaffolding) the manufacturer_id field is blank.
I went to my cars_controller, found the 'create' method that is being used, and tried to add the second line below:
#car = Car.new(params[:car])
#car.manufacturer_id = car.manufacturer.id # <===
This produces a 'NameError in CarsController#create' error, and I see:
undefined local variable or method 'car' for #<CarsController:0x1034642f0>
Rails doesn't seem to like the line I've added. What am I missing to make this work?
Well, you need to have a manufacturer available before you can attach it to the car.
#car = Car.new( params[:car] )
m = Manufacturer.first # => as you can see you must already have one made
#car.manufacturer = m
#car.save
The reason car is undefined is because, well, you haven't defined it. Which car's manufacturer did you want to assign to #car?
So basically you need to make a manufacturer before you make a car. If the form you're filling out has the data for the manufacturer then make sure to put that under a different key in params, like, say, params[:manufacturer] and do a similar thing as you're doing with the car. Maybe like:
#car = Car.new( params[:car] )
#manufacturer = Manufacturer.find_or_create_by_name_and_country( params[:manufacturer][:name], params[:manufacturer][:country] )
#car.manufacturer = #manufacturer
#car.save
In your view, you want to generate a drop-down list for manufacturers (I would assume), so you should do something like this in the form:
<%= collection_select(:car, :manufacturer_id, Manufacturer.all, :id, :name) %>
Then your create action shouldn't need to explicitly set a manufacturer_id because it should have received that from the form.
Related
I have 4 tables:users, observative_session, observations, celestial_bodies.
Each user has many observative_session and each observative_session has many observations. I already put in the model the associations
So in observative_session I have a foreign_key (user_id) to link it to the user and in observation I have one foreign key (user_id) for the user and a foreign key (observative_session_id) for the observative_session plus another foreign key for the celestial_body (celestial_body_id).
I created a form in which I ask the user to insert the name of a celestial body
<%= f.text_field :celestial_body_id, label: 'Celestial body' %>
but I can't save the string as an id so I need to find the id corresponding to the inserted body and save it instead.
I tryed to define a virtual attribute
def celestial_body_name
CelestialBody.where(' ')
end
def celestial_body_name= (name)
celestyal_body_id = CelestialBody.where(name: celestial_body_name)
end
and then I create the new observation
def create
#observation = #observative_session.observations.build(observation_params)
....
end
but I get the undefined method 'observations' for nil:NilClass
I don't understand if I pass the parameters correctly or not.
Thank you for any help.
You should go with some autocomplete solution as https://github.com/bigtunacan/rails-jquery-autocomplete. You need to think about passing additional hidden field as celestial_body_id to create valid association in controller. I think this part should help you -- https://github.com/bigtunacan/rails-jquery-autocomplete#sending-extra-search-fields.
Of course you can still pass name without autocomplete, but it's bad for UI (you never know whether such category exists and you can even make some typo in it) and it will require additional queries on logic on backend side
I am confused about the some Association concepts in Active Records.
I have three models User, Bank and Bankaccount. Both the User and the Bank models "has_many" Bankaccounts and the Bankaccount model "belongs_to" both the User and the Bank models. I use the following syntax to create a Bankaccount through its association with User
#bankaccount = #user.bankaccounts.create(bankaccount_params)
What is the appropriate syntax if I want to create a bankaccount object through both the association with User and the association with Bank?
My second question is related to this one. Right now, because I am not sure how to create a bankaccount through both associations, I handle the association with the Bank by putting the parameter manually
bank_id = params[:bank_id]
However, this seems to trigger some issues down the road when I want to iterate through all the bankaccounts and retrieve the name of the associated bank.
In my view I have
<% #bankaccounts.each do |bankaccount| %>
<%= bankaccount.bank %>
I obtained a list of these
#<Bank:0x007f7a66618ef0>
#<Bank:0x007f7a664c9ab8>
If I tried to get the name of the bank
<% #bankaccounts.each do |bankaccount| %>
<%= bankaccount.bank.name %>
I get an undefined method name for nil class. I do get the name of the bank in the console with these simple lines
bankaccount = Bankaccount.find(1)
bankaccount.bank.name
Could you anyone give me more background on those concepts and provide me with the appropriate syntax to loop accross my collection #user.bankaccount and for each bankaccount retrieve the name of the associated bank?
Thanks.
You'll have to choose one association to create a bankaccount through, then set the second separately:
#bankaccount = #user.bankaccounts.new(bankaccount_params)
#bankaccount.bank = somebank
#bankaccount.save
Or
#bankaccount = #bank.bankaccounts.new(bankaccount_params)
#bankaccount.user = someuser
#bankaccount.save
In addition, I don't see why setting the second association manually with a param would inherently cause the other problems you are experiencing. This should be fine (assuming a bank with this id actually exists):
#bankaccount.bank_id = params[:bank_id]
If you choose to assign a foreign key as a parameter, you can roll it into strong parameters and pass it into the bankaccount model with everything else. For example:
def bankaccount_params
params.require(:bankaccount).permit(:bank_id, ...)
end
You last issue regarding arrays vs. collections depends on what you are trying to do. First, if you are particularly interested in the bankaccount's bank name, make it easier to get:
class Bankaccount
belongs_to :bank
...
def bank_name
bank.name
end
end
For those who buy into such things, this also prevents a Law of Demeter violation.
If you are simply trying to list the names of banks for #bankaccounts in a view, try leveraging Rails partials with something like this:
app/views/bankaccounts/index.html.erb
<%= render #bankaccounts %>
app/views/bankaccounts/_bankaccount.html.erb
<%= bankaccount.bank_name %>
More on this here: http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials
If you're looping over #bankaccounts for another reason, the code you provided should work, given that #bankaccounts represents ActiveRecord relations and not a simple array:
<% #bankaccounts.each do |bankaccount| %>
<%= bankaccount.bank_name %>
<% end %>
Since you're getting an undefined method error, your problem probably stems from how you are building #bankaccounts. If you are doing exactly this...
#bankaccounts = #user.bankaccounts
...and you've verified that everything is properly associated in the console, then your problem is likely unrelated to arrays or collections.
Here's the basic setup:
I have an Order model. An Order has one Address and it accepts_nested_attributes_for :address.
I have a basic order form where I ask a user to input her address. This is handled with nested_fields_for. Everything works great - new addresses are validated and assigned nicely.
However, the problem is that it creates a new Address every time, even if an Address already exists with identical attributes.
I would like to modify the behavior so that if the user-inputted address matches all the attributes for an existing Address, the order assigns the existing Address to itself rather than creating a new one.
The methods I have tried are:
In the controller, try to find an existing Address record with the nested attributes (params[:order][:address_attributes]). If a match exists, delete all the nested attributes and replace them with params[:order][:address_id].
Don't use nested_attributes_for at all and instead override the address= method in the model, then just use the controller to create a new Address based on the parameters and then hand it off to the model.
Both of these solutions seem various degrees of messy. Could somebody please enlighten me on whether this is a controller or model responsibility, and perhaps suggest an elegant way to accomplish this?
Thanks in advance.
Have you tried something like this?
class Order < ActiveRecord::Base
# [..]
before_save :replace_existing_address!
def replace_existing_address!
db_address = Address.where(:city => self.address.city,
:street => self.address.street,
:number => self.address.number).first
self.address = db_address if db_address
end
end
Since I'm asking this as a survey of good ways to do this, I figured I'd offer the solution I'm currently using as well as a basis for comment.
In the Controller:
#new_address = Address.new( params[:order][:address] )
#order.address = new_address
#order.update_attributes( params[:order] )
In the Model:
def address=( address )
return unless address and address.is_a? Address
duplicate_address = Address.where( address_1: address.address_1,
address_2: address.address_2,
[etc. etc.] ).first
if duplicate_address
self.address_id = duplicate_address.id
else
address.save
self.address_id = address.id
end
end
I it's truly a :has_one relationship as you say and not a :has_many, you don't need to explicitly assign the address like you do in your own answer. That's what accepts_nested_attributes is for, after all. This line by itself should work:
#order.update_attributes( params[:order] )
That should create a new address if none exists, and update an existing one.
Your solution may work, but it a) doesn't take advantage of accepts_nested_attributes and b) will leave lots of orphaned addresses in your database.
Hey,
Not a Rails noob but this has stumped me.
With has many through associations in Rails. When I mass assign wines to a winebar through a winelist association (or through) table with something like this.
class WineBarController
def update
#winebar = WineBar.find(params[:id])
#winebar.wines = Wine.find(params[:wine_bar][:wine_ids].split(",")) // Mass assign wines.
render (#winebar.update_attributes(params[:wine_bar]) ? :update_success : :update_failure)
end
end
This will delete every winelist row associated with that winebar. Then it finds all of the wines in wine_ids, which we presume is a comma separated string of wine ids. Then it inserts back into the winelist a new association. This would be expensive, but fine if the destroyed association rows didn't have metadata such as the individual wine bar's price per glass and bottle.
Is there a way to have it not blow everything away, just do an enumerable comparison of the arrays and insert delete whatever changes. I feel like that's something rails does and I'm just missing something obvious.
Thanks.
Your problem looks like it's with your first statement in the update method - you're creating a new wine bar record, instead of loading an existing record and updating it. That's why when you examine the record, there's nothing showing of the relationship. Rails is smart enough not to drop/create every record on the list, so don't worry about that.
If you're using the standard rails setup for your forms:
<% form_for #wine_bar do |f| %>
Then you can call your update like this:
class WineBarController
def update
#winebar = WineBar.find(params[:id])
render (#winebar.update_attributes(params[:wine_bar]) ? :update_success : :update_failure)
end
end
You don't need to explicitly update your record with params[:wine_bar][:wine_ids], because when you updated it with params[:wine_bar], the wine_ids were included as part of that. I hope this helps!
UPDATE: You mentioned that this doesn't work because of how the forms are setup, but you can fix it easily. In your form, you'll want to rename the input field from wine_bar[wine_ids] to wine_bar[wine_ids_string]. Then you just need to create the accessors in your model, like so:
class WineBar < ActiveRecord::Base
def wine_ids_string
wines.map(&:id).join(',')
end
def wine_ids_string= id_string
self.wine_ids = id_string.split(/,/)
end
end
The first method above is the "getter" - it takes the list of associated wine ids and converts them to a string that the form can use. The next method is the "setter", and it accepts a comma-delimited string of ids, and breaks it up into the array that wine_ids= accepts.
You might also be interested in my article Dynamic Form Elements in Rails, which outlines how rails form inputs aren't limited to the attributes in the database record. Any pair of accessor methods can be used.
I have a scenario where I would like to save data to two models from one form.
Basically I have a player which belongs to many teams. So in the new action of players_controller I'd like to have a multiple select box that contains all the teams. then the user can select 2 or 3 of them..click save and they will be saved.
player belonging to many teams is done by a table called playerizations it contains a player_id and team_id columns
so if I want to get all the teams a player belongs to. I just say player.teams
all this relationship is working fine. I would just like to know how to save to playerizations table when new player is created
What I have (it is basically scaffolding model):
def new
#player = Player.new
#teams = Team.all
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #player }
end
view:
<% form_for(#player) do |f| %>
<%= f.error_messages %>
...
....
All Teams<br/>
<%= select_tag 'selected_teams[]', options_for_select(#teams.map {|t| [t.team_name, t.id]}), :multiple => true%>
end
Can I get some hints please? I took a look at railscast regarding this but not much help.
You're describing a has-and-belongs-to-many ("HABTM") relationship but you have not defined it according to Rails convention, so Rails isn't sure how it should update your models.
Player model should say:
has_and_belongs_to_many :teams
Team model should say:
has_and_belongs_to_many :players
This has the happy side effect that not only does "player.teams" give a list of a player's associated teams, but also "team.players" gives the list of the players in a given team.
Your join table must be called "players_teams", because the Rails convention is to use the name of the two models in plural form and joined together in ascending alphabetical order. Renaming your "playerizations" table should be sufficient since it sounds like the table columns are correct.
Your select menu code is almost there; you need something like:
select_tag(
:player_team_ids,
options_for_select( #teams.map { | t | [ t.team_name, t.id ] } ),
{
:multiple => true,
:name => 'player[team_ids][]'
}
)
It's the "name" assignment that contains the 'magic' to get your team IDs array assigned. The first parameter to "select_tag" is just the form field's name of "player[team_ids][]" with the square brackets turned into underscores or stripped off if at the end of the string, thus generating a recognisable and unique ID for use in the output HTML.
You can then save your player model or update its attributes with standard calls to save() or update_attributes() - no need for additional code per se however Rails falters on validations. If you are editing an existing player's details, then a call to "update_attributes" will result in the teams association being updated first. Then the player is updated; if its validations fail, the team changes will have been saved anyway. It's quite simple to patch around; wrap your call to update_attributes() in a transaction and roll back if update_attributes returns 'false' indicating failure.
success = Player.transaction do
player.update_attributes( params[ :player ] ) ) or raise ActiveRecord::Rollback
end
The value of 'success' will end up being 'true' for success or 'nil' for failure. This works because the Rollback exception is caught by the transaction block and does not propagate. Setting 'success' to the evaluated result of the block rather than trying to use local variables means that the code is both Ruby 1.8 and Ruby 1.9-friendly.
This is only necessary when updating existing records with HABTM relationships. It is not required when creating new records.
All of the above code is untested and may contain typing errors, so please use with due care and attention.
For more on HABTM:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
For more on transactions:
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html