Help with Associations in Rails 3 - ruby-on-rails

Ruby: 1.9.2
Rails: 3.0beta3
I need some help with associations in Rails 3.
I have the following models (see excerpts below):
School, State, SchoolLocale
The schools table has the following fields:
id, name, state_id, school_locale_id
The states table has the following fields:
id, abbr, name
The school_locales table has the following fields:
id, code, name
Unfortunately, my data-source didn't have IDs for school_locales. Thus, the data stored in the 'school_locale_id' field in the schools table actually maps to the 'code' field in the school_locales table.
school.rb:
class School < ActiveRecord::Base
belongs_to :state
belongs_to :school_locale
end
state.rb:
class State < ActiveRecord::Base
has_many :schools
end
school_locale.rb:
class SchoolLocale < ActiveRecord::Base
has_many :schools
end
I would like a query for a given school, let's say School.find(1), that would output the school name, the state name and the school-locale name. I assume that I need to add an index to the 'code' field in the school_locales table and somehow specify it as a foreign key, but I'm not certain. Any help would be appreciated.

This doesn't exactly answer your question, but I think it is a useful bit of information. Regarding your use of a states table, let me refer to Surrogate Vs. Natural/Business Keys.
#Ted says here:
Remember there is nothing special about a primary key, except that it is labelled as such. It is nothing more than a NOT NULL UNIQUE constraint, and a table can have more than one.
If you use a surrogate key, you still want a business key to ensure uniqueness according to the business rules.
There's no point in having a state_id foreign key that links to a states table. Each state already has a unique id; its 2-letter abbreviation. This unique id is just as good as a numeric one. And because this data doesn't change often, there's no harm in having it statically defined within your application somewhere.

I'm not really sure about it, but you could try using this:
class SchoolLocale < ActiveRecord::Base
has_many :schools, :primary_key => :code
end
Let me know if it works :]

Related

Query records through its belongs_to relation in Rails 4

I'm hoping for a little clarification on the correct way of querying records through its belongs_to relation in Rails 4.
I would like to find all parts that have a specific category name.
I can make it work with category ID using: Part.where(category_id: "1")
But I can't get it to take with the name.
If I do this:
Part.joins(:categories).where("categories.name = 'cars'").first
I get back this error:
ActiveRecord::ConfigurationError: Association named 'categories' was not found on Part; perhaps you misspelled it?
I don't see where I misspelled anything and I can't figure out why it's not working. Any help would be greatly appreciated!
Category Model:
class Category < ActiveRecord::Base
has_many :parts
end
Parts Model:
class Part < ActiveRecord::Base
belongs_to :category
end
Category Table:
CREATE TABLE categories
(
id serial NOT NULL,
name character varying(255),
CONSTRAINT categories_pkey PRIMARY KEY (id)
)
Parts Table:
CREATE TABLE parts
(
id serial NOT NULL,
category_id integer,
CONSTRAINT parts_pkey PRIMARY KEY (id)
)
Your belongs_to association name is singular (which is correct, as it the standard) in the Part model, but you tried to join using the pluralized association name (you tried :categories). A common mistake; you probably got mixed up between the database naming convention, and the rails association naming convention. Try instead this:
Part.joins(:category).where('categories.name = "cars"').first
Side note:
This is not related to the question, but in case you did not already know, you can use a hash in the where clause if you like as well. It would look like:
where(categories: {name: "cars"})
And the result would be the same. In your case, it is not relevant since you're clearly passing a literal string, but it may come in handy when you're using form data, or something equally non-secure, since the hash method will properly treat the parameter as text, rather than dangerously placing it in the query as-is.
Use: Part.joins(:category)
The association name you pass to joins should mimic your declaration of it: belongs_to :category. Here you use :category, so give joins the same symbol.

Rails Has Many Association

I have few question that bugs me off and need to be answered. Everything is related to the following tutorial Two Many-to-Many
Question 1
Does the join table using has_many need to have an id? or its best practice to remove the id? and add an index and using the two other primary key and set it unique and together?
Question 2
How can it be done in the migration of creating a table?
Question 3
After doing these relationship model and updating the data. I would like to create a new set of data everytime it is updated (to preserve the data). How would a controller would look in the update, new, create model?
Question 4
In the the middle table, I would like to set attributes such has a visible true, or false, how can I set also not just the third table but also the second table arguments
First ... a word of caution: That railscast is very old. There may be syntactical things in that episode that have been dated by new versions of rails.
Question 1
If you are using the has_many through method then you have to have an id column in the join model because you are using a full blown model. As Ryan mentions in the episode, you'll choose this method if you need to track additional information. If you use the has_and_belongs_to_many method, you will not have an id column in your table.
If you want to achieve a check where you do not allow duplicates in your many-to-many association (ie allow the pairing of item a with item b and again allowing another record of item a to item b), you can use a simple validates line with a scope:
validates_uniqueness_of :model_a_id, :scope => [:model_b_id]
Question 2
You can add indices in your migrations with this code
add_index :table_name, [ :join_a_id, :join_b_id ], :unique => true, :name => 'by_a_and_b'
This would be inserted into the change block below your create_table statement (but not in that create_table block). Check out this question for some more details: In a join table, what's the best workaround for Rails' absence of a composite key?
Question 3
I'm not completely clear on what you're looking to accomplish but if you want to take some action every time a new record is inserted into the join model I would use the after_create active record hook. That would look something like this.
class YourJoinModel < ActiveRecord::Base
after_create :do_something
def do_something
puts "hello world"
end
end
That function, do_something, will be called each time a new record is created.
Question 4
Using the has_many through method will give you access to the additional attributes that you defined in that model on both sides of the relationship. For example, if you have this setup:
class Factory < ActiveRecord::Base
has_many :widgets, :through => :showcases
end
class Widget < ActiveRecord::Base
has_many :factories, :through => :showcases
end
class Showcases < ActiveRecord::Base
belongs_to :factory
belongs_to :widget
attr_accessiable :factory_id, :widget_id, :visible
end
You could say something like
widget = Widget.first
shown = widget.showcases
shown.first.visible
or
shown = widget.showcases.where( :visible=> true )
You can also reach to the other association:
shown.first.factory
The reason for having an id column in an association is it gives you a way of deleting that specific association without concerning yourself with the relationship it has. Without that identifier, associations are hard to define outside of specifying all foreign keys.
For a trivial case where you have only two components to your key, this isn't that big a differentiator, but often you will have three or more as part of your unique constraint and there's where things get tricky.
Having an id also makes the relationship a first-class model. This can be useful when you're manipulating elements that have associated meta-data. It also means you can add meta-data effortlessly at a later date. This is what you mean by your "Question 4". Add those attributes to the join model.
Generally the join model is created like you would any other model. The primary key is the id and you create a series of secondary keys:
create_table :example_things |t|
t.integer :example_id
t.integer :thing_id
end
add_index :example_joins, [ :example_id, :thing_id ], :unique => true
add_index :example_joins, :thing_id
The main unique index serves to prevent duplication and allows lookups of key-pairs. The secondary serves as a way of extracting all example_id for a given thing_id.
The usual way to manipulate meta-data on the join model is to fetch those directly:
#example_things = #example.example_things.includes(:thing)
This loads both the ExampleThing and Thing models associated with an Example.

Using Retrieval Multiple Objects for another Retrieval in Active Records/Ruby on Rails

Kind of new to Ruby/Rails, coming from c/c++, so I'm doing my baby steps.
I'm trying to find the most elegant solution to the following problem.
Table A, among others has a foreign key to table B (let's call it b_id), and table B contains a name field and a primary (id).
I wish to get a list of object from A, based on some criteria, use this list's b_id to access Table B, and retrieve the names (name field).
I've been trying many things which fail. I guess I'm missing something fundamental here.
I tried:
curr_users = A.Where(condition)
curr_names = B.where(id: curr_users.b_id) # fails
Also tried:
curr_names = B.where(id: curr_users.all().b_id) # fails, doesn't recognize b_id
The following works, but it only handles a single user...
curr_names = B.where(id: curr_users.first().b_id) # ok
I can iterate the curr_users and build an array of foreign keys and use them to access B, but it seems there must be more elegant way to do this.
What do I miss here?
Cheers.
Assuming you have following models:
class Employee
belongs_to :department
end
class Department
has_many :employees
end
Now you can departments based on some employee filter
# departments with employees from California
Department.include(:employees).where(:employees => {:state => "CA"}).pluck(:name)
For simplicity, let's take an example of Article and Comments, instead of A and B.
A Comment has a foreign key article_id pointing at Article, so we can setup a has_many relationship from Article to Comment and a belongs_to relationship from Comment to Article like so:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
end
Once you have that, you will be able do <article>.comments and Rails will spit out an array of all comments that have that article's foreign key. No need to use conditionals unless you are trying to set up a more complicated query (like all comments that were created before a certain date, for example).
To get all the comment titles (names in your example), you can do <article>.comments.map(&:title).

Newbie: access attributes of model object

I have two model classes: Cars and Customers,
Model Cars:
class car < ActiveRecord::Base
#car has attribute :town_code
has_many :customers
end
Model Customers:
class customer < ActiveRecord::Base
# customer has attribute :first_name, :last_name
belongs_to :car
end
In my controller, I have the following code:
my_customer = Customer.find_all_by_first_name('John')
p my_customer.last_name
p my_customer.car_id
But I got no attribute 'car_id' error, I also got no attribute 'last_name' error.
---Question 1:---
I checked my database, I do have 'car_id' and 'last_name' columns on my customer table. Why I can not access them in the way how my controller code does?
---Question 2:---
but the code : my_customer.map(&:car_id) is working for accessing car_id, however, I do not quite understand the code .map(&:car_id), what does it do? Can anyone explains to me?
The reason you aren't able to do my_customer.last_name is that my_customer is not a Customer here but an array of Customers, since you did find_all. That's also why my_customer.map(&:car_id) works. What that bit of code means is: For each object in the array my_customer, call the method car_id and insert the results into a new array -- and return that new array.
If customer belongs to car, you need a car_id in the customer table (which corresponds to an id column in the car table). Also, you shouldn't have last_name in the car table, but rather in the customer table.
It sounds like you may need to step back and gain a better understanding of ActiveRecord associations. It's not clear to me why a customer would belong_to a car, anyway.

Rails active record model relationship - one model belonging to three models

I have situation where a single model needs to have three foreign ids. This table has the information which belongs to three models.
e.g. -
Three models - Keyboard, Mouse, Monitor
Now i have fourth model details - which has information about what will happen for any combination like keyboard1, mouse1 and monitor1.
So what should be good design pattern for this ?
Right now what i use is Detail.find_by_keyboard_id_and_mouse_id_and_monitor_id(keyboard1id, mouse1id, monitor1id) #=> Detail1
Which of course is not a best way to go...
I don't think what you have is a bad design. There probably aren't too many options for how this should be implemented, but you should think about how you want to use it. Personally, I wouldn't call the model Detail since it doesn't tell you anything about what the model really is. Something like HardwareConfiguration is more explicit. Maybe too lengthy, but you could shorten it to HardwareConfig or Configuration, depending on your taste.
This model should have an id and the three foreign keys. You should add database indexes to each foreign key field as well.
class Detail < ActiveRecord::Base
belongs_to :keyboard
belongs_to :mouse
belongs_to :monitor
end
Then each hardware model would have_many :details like:
class Keyboard < ActiveRecord::Base
has_many :details
end
You could look up the detail combo by its ID, or any combination of foreign keys.
Detail.find(id)
Detail.find_all_by_keyboard_id(keyboard_id)

Resources