How to use foreign key in model and form builder? - ruby-on-rails

I have two models: User and Location as below:
class User < ActiveRecord::Base
attr_accessible :location, :password, :user_name, :password_confirmation
validates :location, :user_name, :presence => true
validates :password, :presence => true, :confirmation => true
has_one :location, :foreign_key => 'location'
end
class Location < ActiveRecord::Base
attr_accessible :loc_id, :loc_name
belongs_to :user, :foreign_key => 'loc_id'
end
You can see that I use the custom foreign_key for the models. I use form builder to build a user sign up form, but when I submit data the error occurs:
Location(#2170327880) expected, got String
I use simple_form to build the form, related code is:
= f.input :location, :collection => Location.all.collect {|c| [c.loc_name, c.loc_id]}
How can I resolve this problem? Or must I use the default foreign_key like location_id for the association?
Thanks.
Update:
When I rename the location field in User model to loc_id and remove the :foreign_key like this:
class User < ActiveRecord::Base
attr_accessible :loc_id, :password, :user_name, :password_confirmation
validates :loc_id, :user_name, :presence => true
validates :password, :presence => true, :confirmation => true
has_one :location, :foreign_key => 'location'
end
class Location < ActiveRecord::Base
attr_accessible :loc_id, :loc_name
belongs_to :user
end
It works fine. But I still want to know how to associate the User and Location model.
P.S. I use Location model to store the country code and country name, which will never update by User.

It sounds like you actually want to have
class User < ActiveRecord::Base
belongs_to :location
end
class Location < ActiveRecord::Base
has_many :users
end
This means that a user has a location_id column. If you do things the other way around (user_id column on location) then a given location can only be associated to one user. The rails way is that location_id on users 'points' at the id column in the locations table. If you want it to point at a different column, use the :primary_key option (The :foreign_key option would be if you wanted the column on users to be called something other than location_id)
In terms of the form, you can't do f.select :location - forms don't know how to transfer a complicated object like that. In these cases you want to set the form to control the location_id attribute, i.e.
= f.input :location_id, :collection => Location.all.collect {|c| [c.loc_name, c.id]}
If you go down the route of having the location id column refer to the loc_id column on location, then you'd need to change that to be
= f.input :location_id, :collection => Location.all.collect {|c| [c.loc_name, c.loc_id]}
Personally if you're only just starting out with rails I'd stick to the defaults

It looks like you're misusing foreign key. In the User model, you should have just has_one :location and the location model should have a user_id attribute. In the location model, you only need to write belongs_to :user. A foreign key is always an index into another (foreign) table.

class User < ActiveRecord::Base
# stuff
has_one :location
end
class Location < ActiveRecord::Base
# more stuff
belongs_to :user
end

If what you ultimately want to do is select all users with the same location, you might want to set the models up a little differently. Right now, because each user has its own location record in the location table, you would end up with duplicate locations, and you'd have to find all of these for a unique location and extract the user_ids from them.
Instead do this
class User < ActiveRecord::Base
has_one :placement
has_one :location, through: :placement
end
class Placement < ActiveRecord::Base
belongs_to :user
belongs_to :location
end
class Location < ActiveRecord::Base
has_many :placements
has_many :users, through: :placements
end
. As for the migrations, Placement should have a :user_id and :location_id. You can drop the :user_id that you currently have in Location. What this code says is that we have many users and we have many unique locations, and we place users in unique locations by creating placements, which indicate that a user with :user_id is located in location with :location_id. Also, don't forget to add a the line
add_index :placements, [:user_id, :location_id], unique: true
so that you can't place a user in a location more than once.
EDIT: Forgot to add: you can get all users in a location by simply getting the location record and calling location.users

Related

Rails 4 drop down select list different model

New to Rails so go easy on me :-)
I have 2 models: User and Role:
class User < ActiveRecord::Base
has_many :roles
accepts_nested_attributes_for :roles
validates_presence_of :role_id
end
class Role < ActiveRecord::Base
belongs_to :user
end
User has a role_id for the foreign key.
All I'm trying to do is be able to select a role for the user in the users/new form. I know it's easy, but I cannot seem to figure it out...I've literally read for hours today trying to figure it out. The drop down select list appears in the view, but it always fails validation (like it shows up, but never actually associates what the user selects with the User.role_id)
Here is what I have in my form partial to show the drop down:
<%= f.collection_select :role_id, Role.all, :id, :name %>
Can anyone point me in the right direction? Maybe I have to use some sort of nested forms, but nothing I have tried seems to work and this is what I currently have. Do I have to do something in my controller?
If User has many roles, your User model must not have a field: user_id, I think, and I hope, that Users Have and Belongs to many Roles. Then you need a third model:
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, through: :user_roles
end
class Role < ActiveRecord::Base
has_many :user_roles
has_many :users, through: :user_roles
end
class UserRole < ActiveRecord::Base
belongs_to :user
belongs_to :role
validates_presence_of :role_id, :user_id
end
In your User form you can use this to update relations (look: :role_ids in plural)
<%= f.collection_select :role_ids, Role.all, :id, :name, {}, {multiple: true} %>
And the validation is now in UserRole model.
Edit: If you are using Rails 4.x you need to permit params for a collection of role_ids.
params.require(:user).permit(:user_field1, :user_field2, ... , role_ids: [])

Incrementing value if it exists in JOIN table

Models:
class Location < ActiveRecord::Base
attr_accessible :longitude, :latitude, :radius, :name
has_many :statistics
end
class Statistic < ActiveRecord::Base
attr_accessible :stat_type_id, :updated, :count, :location_id
belongs_to :location
belongs_to :stat_type
end
class StatType < ActiveRecord::Base
attr_accessible :id, :name
has_many :statistics
end
I can update a record like this:
statistic = ##location.statistics.build(:count => 1, :updated => updated, :stat_type_id => ##stat_type.id)
statistic.save
Currently a new record is always created. However, I want to change this so count gets incremented rather than given the value 1.
I know increment exists: http://apidock.com/rails/ActiveRecord/Persistence/increment but I'm not sure how to use it in my case (with this JOIN). How can I use it?
Thanks.

find_or_create on a has many through relationship

I have a has many through relationship in my app:
Shows has many Bands through => Lineups
Bands are unique by :name
class Show < ActiveRecord::Base
attr_accessible :city_id, :title, :dateonly, :timeonly, :image, :canceled, :venue_attributes, :bands_attributes
belongs_to :city
belongs_to :venue
has_many :lineups
has_many :bands, through: :lineups
has_and_belongs_to_many :users
end
class Lineup < ActiveRecord::Base
belongs_to :show
belongs_to :band
end
class Band < ActiveRecord::Base
attr_accessible :name, :website, :country, :state
has_many :lineups
has_many :shows, through: :lineups
validates :name, presence: true
validates_uniqueness_of :name
before_save :titleize_name
private
def titleize_name
self.name = self.name.titleize
end
end
New Bands are created like this:
(lets say we have a show record already saved called s1)
> s1.bands.new(name: "Wet Food")
> s1.save
Right now this will only save if a band named "Wet Food" doesn't already exist
In which model is the best place to do a Band.find_or_create in this relationship so that an existing band can be used if one with the same name exists?
This is generally the type of call that would go in a Controller (or maybe a service object), but not in a Model. It really depends on the particular user flow that you're trying to accomplish in your app. Basically, where ever you are already using s1.bands.new, you could use this instead :
s1.bands.where(name: 'Wet Food').first_or_create

rails_admin plugin issue with nested form on a has_many :through relationship

I have what seems to be a cookie cutter problem that even has an associated wiki page here: https://github.com/sferik/rails_admin/wiki/Has-many-%3Athrough-association
I'll try to be brief. I have a has_many :through relationship in an application I'm building. The models involved are the following:
Athlete, AthleteRole, SportRole.
The sport_roles table has a list of generic roles that an athlete can have such as first baseman, second baseman, etc.
The athlete_roles table is the many to many join table that contains an athlete_id and a sport_id.
My models are defined below with code examples. I simply want to be able to create an Athlete and associate them with 1+ sport roles (which will ultimately create 1+ new records in the athlete_roles table). It shouldn't ask me for an athlete_id as the athlete won't have an id until save is called on the backend and validation passes. I don't need to be able to create new sport_roles here. We'll assume all roles that the new athlete being created can take on have already been predefined.
** EDIT **
To clarify, my question is, how do I get to pick one or multiple existing sport roles for an athlete using the rails_admin plugin, NOT in a stand alone form? I do not wish to create new sport roles, but I want to be able to pick an existing one or two when creating an athlete and have that data reflected in the athlete_roles table.
Code below.
class Athlete < ActiveRecord::Base
has_many :athlete_roles, :dependent => :delete_all, :autosave => true, :include => :sport_role
has_many :sport_roles, :through => :athlete_roles
attr_accessible :first_name
attr_accessible :middle_name
attr_accessible :last_name
attr_accessible :suffix_name
attr_accessible :birthdate
# FOR RAILS_ADMIN
# for a multiselect widget:
attr_accessible :sport_role_ids
accepts_nested_attributes_for :athlete_roles, :allow_destroy => true
attr_accessible :athlete_roles_attributes
end
class AthleteRole < ActiveRecord::Base
attr_accessible :athlete_id
attr_accessible :sport_role_id
# Associations
belongs_to :athlete
belongs_to :sport_role
# validations
validates :athlete_id,:presence=>true,:uniqueness=>{:scope => [:sport_role_id], :message => "is already associated with this Sport Role"}
validates :sport_role_id,:presence=> true
end
class SportRole < ActiveRecord::Base
has_many :athlete_roles, :dependent => :delete_all
has_many :athletes, :through => :athlete_roles
attr_accessible :name
attr_accessible :short_name
attr_accessible :description
attr_accessible :created_at
attr_accessible :updated_at
attr_accessible :athlete_ids
attr_accessible :athlete_role_ids
validates :name, :presence => true
validates :short_name, :presence => true
validates :description,:length=>{:maximum => 500, :allow_nil => true}
end
I think the problem here resides in your model.
A model like you are describing, is a has and belongs to many relation and should look like so:
athlete:
class Athlete < ActiveRecord::Base
has_and_belongs_to_many :sport_roles
end
sports role
class SportRole < ActiveRecord::Base
has_and_belongs_to_many :athletes
end
The migrations should look like:
athlete
class CreateAthletes < ActiveRecord::Migration
def change
create_table :athletes do |t|
t.timestamps
end
end
end
sports role
class CreateSportRoles < ActiveRecord::Migration
def change
create_table :sport_roles do |t|
t.timestamps
end
end
end
relation between athletes and sports roles
class SportRolesAthletes < ActiveRecord::Migration
def change
create_table :sport_roles_athletes , :id => false do |t|
t.integer :sport_role_id
t.integer :athlete_id
end
end
end

Has-Many and Belongs-to-one Relation

I'm trying to keep track of how many signup_conversions a user creates.
Therefore, I have those two following models:
signup_conversion.rb
class SignupConversion < ActiveRecord::Base
belongs_to :user
belongs_to :convertee, :class_name => "User", :foreign_key => 'convertee_id'
attr_accessible :convertee_id
end
user.rb
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
belongs_to :signup_conversion
has_many :signup_conversions
end
Would this work that way? Or am I missing something crucial here?
I haven't tried the code, but I give you some tips that hope you find useful.
I think every has_many/has_one statement should have its correspondent belongs_to, so three belongs_to and one has_many doesn't look good.
I'm not sure has_one :signup_conversion and has_many :signup_conversions would play well together, so I'd rather change the names. I changed the other names as well to try to make the associations clearer although I'm not sure I fully understand the real concepts they represent. You will probably come up with better names.
By default, the foreign key is guessed adding the suffix _id to the association name, so you don't need to specify it in this case. Also, I don't think you need to make that attribute accessible, at least not for the association to work.
signup_conversion.rb
class SignupConversion < ActiveRecord::Base
belongs_to :owner , :class_name => "User"
belongs_to :convertee, :class_name => "User"
end
user.rb
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_one :owned_signup_conversion , :class_name => "SignupConversion"
has_many :triggered_signup_conversions, :class_name => "SignupConversion"
end
You have more code than you need here; you only need
class SignupConversion < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_many :signup_conversions
end
Then you need a user_id column in your signup_conversions table, and you call
#user.signup_conversions
in your views or controller.

Resources