Ruby on Rails/Activerecord mySQL modeling - mysql

This is a pretty simple question really, but let's say I'm creating a model for Person. Person obviously has first name, last name, age, etc. But the person also has contact info consisting of things like address line 1, address line 2, city, state, zip, country, phone 1, phone 2, etc...
Does it make more sense to create a model for Person and then list that contact information as tables in the model or to also create, say, a ContactInfo (or Address, etc) model, then associate the Person to ContactInfo through an association (Person has_one ContactInfo/Person has_one Address/Address belongs_to Person, etc)?
Which of these is a better approach and what are the benefits/drawbacks to each method?
edit: in re to j..
So with this approach, would I have to then create an Addressable model?
script/generate model Addressable
class Addressable < ActiveRecord::Base
#stuff here?
end
or is this unnecessary?
Also, would i need to add this line to the create_users.rb:
t.references :addressable, :polymorphic => true
I feel like I'm missing something, but I'm not sure what. I appreciate the help a ton, btw! Thanks!

I'd create separated tables/models for address, phone and stuff like this and would make them polymorphic. Like this:
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
end
class Person < ActiveRecord::Base
has_one :address, :as => :addressable
end
I believe this is the best way because later you may need to add, for example, a Company model and it'll be easy to make it addressable.
Edit
Using the address as example, you'd need an Address model, not Addressable.
And you'll have to add
t.references :addressable, :polymorphic => true
or
t.belongs_to :addressable, :polymorphic => true
to your create_addresses migration, so you'll have the addressable_id and addressable_type in the addresses table.
Let me know if you have any other doubts :]

the answer above makes sense, but think about how many fields you need and how many records you have to manage. creating a table for each additional field may be too much effort.
another approach could be something more flexible: create a table (say, person_details) with 3 fields: person_id:integer, field_name:string, field_data:string, then the model:
class PersonDetail < ActiveRecord::Base
belongs_to :person
end
this way you can add whatever additional field you need: phone1..phoneN, address1..addressN, and so on.
another similar approach is to pre-determine fields names, to avoid different labels during inserts:
class PersonDetail < ActiveRecord::Base
belongs_to :person
FIELD_NAMES => { 'Address' => 1, 'Phone' => 2)
end
in this case you'll declare the field_name as integer (because it stores only the value of the hash, not a string).

Related

Using form to update has_many through join table

I am short of implementation ideas for my rails project. at this point it seems impossible to implement in rails.
I am cloning a sort of accounting software into a web app using rails framework for small manufacturing companies to keep track of their products stock in their different distribution branches.
I have 3 different models: "Product", "Branch" & "Accumulator"
class Branch < ActiveRecord::Base
has_many :accumulators
has_many :products, :through => :accumulators
end
class Product < ActiveRecord::Base
has_many :accumulators
has_many :branches, :through => :accumulators
def self.search(search)
if search
where('name LIKE ?', "%#{search}%")
end
end
class Accumulator < ActiveRecord::Base
belongs_to :product
belongs_to :branch
end
I am new to rails and after reading up on many to many associations I came across using collections to add products to a branch "#branch.products << Product.all"
Is there a possible way to use a form "number_field_tag" in the branch show view to add multiple of specific products into the join table?
eg
I want to add 10 of (Product) called "garden eggs" to a (Branch) called "lagos branch" to the (Accumulator) join table using a form in Branches show view?
Congratulations for choosing :has_many, through: you will not regret it.
Is the relationship between a product and a branch static?
Or does it change a lot?
In your Accumulator model, add an integer field called amount (count can have conflicts). Then you either create a form for your Accumulators or you add a nested form with for example Cocoon.
This way you can add Accumulators to your Branch with a certain Product and a certain amount.
Off topic:
Here is an article about why has_many through has some advantages:
http://blog.flatironschool.com/why-you-dont-need-has-and-belongs-to-many/
If you have problems with forms I can really recommend SimpleForm and for nice Javascript-assisted fields I recommend Select2.
If the table accumulators needs to save only two things: product_id, and branch_id, you can easily use has_and_belongs_to_many associations.
class Branch < ActiveRecord::Base
has_and_belongs_to_many :products, join_table: 'accumulators'
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :branches, join_table: 'accumulators'
end
And now, there is no need for the third model.
As far as it goes, how to add a relationship, it's pretty easy in this case:
branch = Branch.last
branch.products << Product.create # you don't need to touch the middle table.
Instead of using number_field_tag to ask for plain ids, you can use something fancy like jQuery Chosen Plugin. This plugin will allow you to use tag like input, and will send the ids to the server separated by ,'s.

Save model id as foreign key through has_one / belongs_to

I'll briefly explain my situation: I have a model called "tax" which belongs_to a "user" and of which a "user" has_one.
In my users table I have a column called "tax_id" which I want to store the id of the tax model when a user creates one.
Currently, in my tax model the create function looks something like this:
class Tax < ActiveRecord:Base
belongs_to :user
tax = Tax.new(income: income, taxes: taxes, rrsp: rrsp)
tax.save
and then in the taxes_controller file, the create function looks like this:
def create
#tax = Tax.new(secure_params)
#tax.user = User.find(current_user.id)
if #tax.save
redirect_to show_tax_path(current_user.tax)
else
render :new
end
end
(secure_params) being the strong parameters for the field inputs set in a private definition.
Now, someone mentioned that I may have better luck if I use build but unfortunately I couldn't get it to work at all, something to do with how I'm using current_user (devise). Currently, my setup works just fine, other than saving the tax model id in the user model column "tax_id" like I said.
I'm wondering if I perhaps need to add a foreign ID key to either the belongs_to or has_one statement, even though I was under the impression that the "link" should be made automatically as long as the column was named "[model]_id"
try using
user.build_tax
I think this might help you out.
The build syntax for has_many association:
user.taxes.build
The build syntax for has_one association:
user.build_tax # this will work
user.tax.build # this will throw error

Preventing duplicates via a custom foreign key in has_many :through

I'm trying to implement a two-way has_many :through association between a User model and a Location model using a UserLocations join table. This will enable setting user locations with built in ActiveRecord methods, ie. #user.locations = [<Location 1>, <Location 2>, ...]. My goal is to not associate locations to users individually, but rather for users to add and remove them, one or more at a time, via another field: :zip_code. This means that when a user adds a zip code, ActiveRecord should insert a single record into UserLocations (something like INSERT INTO user_locations (user_id, zip_code) VALUES (1, 12345)). Then, when #user.locations is called, ActiveRecord should join by :zip_code and get the matching location(s). My current implementation works, except that one INSERT into UserLocations is generated for each location associated with a zip code.
class User < ActiveRecord::Base
has_many :user_locations
has_many :locations, through: :user_locations
end
class UserLocation < ActiveRecord::Base
belongs_to :user
belongs_to :location, primary_key: :zip_code, foreign_key: :zip_code
end
class Location < ActiveRecord::Base
has_many :user_locations, primary_key: :zip_code, foreign_key: :zip_code
has_many :users, through: :user_locations
end
Things I've tried:
validates_uniqueness_of :zip_code, scope: :user_id - just throws a validation error and prevents all record creation
has_many unique: true - doesn't prevent duplicate DB queries
add_index unique: true for (user_id, zip_code) - would at least prevent duplicate entries from being created, but I'm trying to prevent unnecessary queries entirely
Using questions like this one for guidance hasn't gotten me any closer. Is what I'm trying to do possible without using my own methods to get/set user locations?
First of all, I'm not very experienced in rails yet, but I'll still try to help :)
What I would do is not using zipcodes as a key. When a user inputs zip codes you look up the code in the Location:
#zip_code = Location.where(zipcode: user_input).first
#zip_code.user_locations.create!(user_id #some other stuff you want)
This way you store the id of the location into the user location and no duplicates are made. You can then generate user locations by joining the UserLocation and Location.
But as I said, there may be a better way of doing this as I'm beginner.
Stop me if I'm wrong :)
You have zipcodes in your locations table (i.e: 111, 222, 333) When a user selects a zipcode of '111' for him self, his record is associated with the existing locations record; but when a user selects a zipcode of '444' a new locations record is created and link to that user. Next use that selects '444' will be linked to this same record.
If my assumption if correct, you should have:
validates_uniqueness_of :zip_code (without scope) in your Location model
in your User model while creating/updating you could use Location.find_or_create_by(params[:zipcode])
This is pseudo-code (don't copy-paste it), I don't exactly know how your code is writen, but my point is for you to have a look at find_or_create, I believe it could be your solution
It looks like you have the association setup correctly.
When you have a has_many association in rails and want to do something like this:
#user.locations = [<Location 1>, <Location 2>, ...]
Rails will create individual INSERT statements for each location in the array, although it will do a bulk DELETE for you. If you want it to do bulk INSERT statements, you'll need to roll your own code or look into the activerecord-import gem to do this.
As for the duplicates, if you are only doing the above code, there shouldn't be duplicate record errors unless there are duplicates in that location array, in which case you should call uniq on it first.

DataMapper- can I avoid intermediate tables?

I am a total beginner at DataMapper, and have two models:
class ThirdPartyAccount
include DataMapper::Resource
property :access_token, String, :length => 500
belongs_to :user
end
class User
include DataMapper::Resource
property :id, Serial
property :first_name, String
has n, :third_party_accounts, :through => Resource
end
Looking at the SQL logs, it appears to create two tables- users, third_party_accounts and third_party_account_users to join the two. It doesn't appear that the last table is needed- surely the third_party_account table just needs to use it's user_id field to map directly to the user table? Have I accidentally created a many-to-many relationship here?
It's due to this line:
has n, :third_party_accounts, :through => Resource
:through => Resource tells DataMapper to that it's a "has-and-belongs-to-many" relation (each 3rd party account belongs to multiple users and each user has multiple 3rd party accounts), which requires an intermediate table. If this is just a has-many relation (each user has many 3rd party accounts, but each account only belongs to one user), you should just use:
Class User
...
has n, :third_party_accounts
end
See http://datamapper.org/docs/associations.html for more info.

Database and model setup in Ruby On Rails

I'm pretty new to database setup in Ruby and need help in setting it up correctly along with the models. Can anyone help?
Basically I have an Organisation, which will have at least 2 types of Users - Member and Admin User. Both the Organisations and the Users have an Address.
I was thinking this was basically three tables - Organisation, User and Address, but then got really confused when trying think about models and foreign keys.
Can anyone suggest the best way to organise this?
I'm running Rails 3 with a mySql database.
Thanks for your time
Sniffer
I like a lot of Adam Tanner's answer, but I would set it up a little differently. First, the way an Organization associates with admins doesn't work as described - you'd have to have a different foreign key in your user table, and specify that in the has_one :admin association. But I don't think that's a good path anyway, because it limits you to one admin per organization, and limits a user belonging to one organization.
My version is slightly more complicated, but I think it gets the job done well. First, admin should be a role that a user has or doesn't have with an organization. I'll address the user/org issue first, and save the address issue for later.
Here are the migrations, which you can enhance with whatever other fields they need:
create_table :organizations do |t|
# your fields go here
end
create_table :users do |t|
# your fields go here
end
create_table :memberships do |t|
t.integer :user_id
t.integer :organization_id
t.boolean :is_admin
end
add_index :memberships, [:user_id, :organization_id]
As you can see, we're adding a memberships table, which is going to connect users and organizations. We also add an index to speed up the association a little. Now for the models:
class Organization < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
class User < ActiveRecord::Base
has_many :memberships
has_many :organizations, :through => :memberships
def membership_in organization
self.memberships.detect{|m| m.organization = organization}
end
def is_admin_for? organization
self.membership_in(organization).is_admin?
end
def set_admin_for organization, value
self.membership_in(organization).update_attribute(:is_admin, value)
end
end
class Membership < ActiveRecord::Base
belongs_to :organization
belongs_to :user
end
Here, we're connecting our users and organizations through memberships. A user can be an admin for any of the organizations they belong to. I've created a few methods to set and get the admin status of a user in an organization, in the user model.
Next the addresses: I've already tackled this one in a blog post of mine:
http://kconrails.com/2010/10/19/common-addresses-using-polymorphism-and-nested-attributes-in-rails/
If you have any questions, please ask. Good luck!
UPDATE
Edward M. Smith pointed out in the comments that my admin methods aren't very fault-tolerant. I was trying to keep the code as clean as possible for the example, but he has a point. So here's the beefier version that accounts for trying to use a membership in an organization the user isn't part of:
def is_admin_for? organization
membership = self.membership_in(organization)
return false if membership.nil?
membership.is_admin?
end
def set_admin_for organization, value
membership = self.membership_in(organization)
return false if membership.nil?
membership.update_attribute(:is_admin, value)
end
As always, test-driven development is best, but I usually don't have the time to do that for stackoverflow questions :)
class Organization < ActiveRecord::Base
has_one :address, :as => :addressable
has_many :members, :class_name => "User"
end
class User < ActiveRecord::Base
has_one :address, :as => :addressable
belongs_to :organization
end
class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true
end
I have removed the admin association, partly because it didn't work anyway, mostly because as Jaime said it is the wrong way to go about it (most developers use some sort of roles system). See Jaime's post for a good way about creating an extendable role system.
Hope this helps you out!