How do I associate a model with multiple models? - ruby-on-rails-3

I'm a brand new developer so this might be a dumb question.
I am trying to setup a webapp which will store stats on geographic data.
I have a model called Stat which has fields for basic statistical information (median, variance, avg, etc.).
I have setup models for each geographic units City, Zip, Sub-Zip
What I want to do know is associate the Stat model with Cities, Zips, and Sub-Zips; that is every stat model belongs to either a city, zip, or sub-zip while a geographic unit (city,zip,sub-zip) can have multiple stats.
How do I setup the associations and migration to do this? I've looked through the Rails guides but it doesn't seem to cover a relationship where an object can belong to different models (but only one of them). Or should I set up my models differently?

You said:
every stat model belongs to either a city, zip, or sub-zip while a
geographic unit (city,zip,sub-zip) can have multiple stats.
So I think you want to set up Polymorphic Associations:
With polymorphic associations, a model can belong to more than one
other model, on a single association.
So your association maybe like this:
class Stat
belongs_to :statsable, polymorphic: true
end
class City
has_many :stats, as: :statsable
end
class Zip
has_many :stats, as: :statsable
end
class Subzip
has_many :stats, as: :statsable
end

This can be done with rails association.
Following link explain you this in details,
http://guides.rubyonrails.org/association_basics.html

Related

Rails Postgres hstore or jsonb to store many to many relations

There is a table 'Products' with columns (id, tile, price, description, shop_id, timestamps)
Column price and shop_id are dynamic. The rest of column are static.
Also there is a table 'Shops' with columns (id, name, timestamps).
Shop has many products. Product can migrate among all shops and products.shop_id can be blank.
I want to have history about which products a shop had. For example, yesterday shop had products and their prices. Day before yesterday shop had other products and/or other their prices. So I want to have something like
'ShopHistory' (id, data, date, timestamps), where data is hstore or json. So it should have hash like { key: value}, where key is id of product and value is its current price. But I would like to joins data column with products in order to know about title and description of products. I have no idea how to do that.
It looks like ShopHistory is a join table between shop and products, but all information are stored in one row.
Could we help me? Maybe anyone knows a better way to implement all of this. Any thought and articles are welcome.
Thanks!
P.S. I use rails(ActiveRecord) and PostgreSQL, but answers from guys who know only Postgres are good for me too.
I get your intent, but frankly I don't think it's going to work out for you in a straightforward, easy way. (As a rule of thumb, in ruby and in rails, if it isn't straightforward, you're probably not doing it the ruby/rails way).
Why do I know this? Because I tried before to do a very similar thing to what you want to do with an hstore, with no luck:
Can I use ActiveRecord relationships with fields from an Hstore?
As a better solution (what worked out for me in the end), please consider making an intermediate model to match Shops to Products, (maybe you'll want to name this intermediate model something like Shop_product, with a join table shops_products, but the name is up to you) then join both models using the intermediate model with a has_many through: relationship as detailed here:
http://edgeguides.rubyonrails.org/association_basics.html#the-has-many-through-association
Something like:
class Shop << ActiveRecord::Base
has_many :shop_products
has_many :products, through: :shop_products
end
class ShopProduct << ActiveRecord::Base
belongs_to :shop
belongs_to :product
end
class Product << ActiveRecord::Base
has_many :shop_products
has_many :shops, through: :products
(There's more info about how to create all of that in the link, I recommend that read)
Now you will have an association set up, so you can get:
Shop.find_by_id(1).products
Product.find_by_id(1).shops
Finally, I think you can use ActiveRecord scopes to solve the second computational problem that you need to solve (that is, find the price per day in the past).
Scopes will allow you, ideally, to do queries like:
Shop.products.active_yesterday
class Product << ActiveRecord::Base
scope active_yesterday -> { where('updated_at BETWEEN ? AND ?', 1.day.ago.beginning_of_day, 1.day.ago.end_of_day) }
has_many :shop_products
has_many :shops, through: :shop_products
end
All of my code is not production-ready and not tested, but I think my examples and links should be enough to get you on the right path.
Let me know if you need more help and I can try to help.

Why does this association work?

I have a small app that allows users to upload recipes and save favourite recipes. I have a separate model called country that has all the countries of the world within it which then allows users to select one from a dropdown.
Initially I had the association as
recipe
has_one :country
country
belongs_to :recipe
After some research the correct association is
recipe
belongs_to :country
country
belongs_to :recipe
The foreign key country_id going within the recipe model.
I'm going to do some more reading but was wondering if someone could explain as to why it is this association and not the first one
I guest you want to build association like this:
country can has many recipe
recipe belongs to one country
If so, you should define association is:
country:
has_many :recipes
recipe:
belongs_to :country
And i think your second association is also incorrect.
When you define belongs_to :country in Recipe model, it means your Recipe table must have a column called country_id. It is a foreign key to Country model.
In the first define association, the Country model will have a column called recipe_id, so, every country just has only one recipe, that's not what you want, right? Why it's not work? Because with one country you have only one record, so one country can have only one recipe, accessed through recipe_id.
With first association, your association is One-to-One (One Country has one Recipe), while you actually want your association is One-to-Many ( One Country has Many Recipe). So it's reason why first association not works (second too).
The main thing you need to remember here is, what model you put a belongs_to association, that model will have a column called 'association name'_id. The different between using has_one and belongs_to only is where you put foreign key and the meaning of association. Check here to clearer.
I'm not sure this is the right association. The belongs_to association is always used in the model that has the foreign key (see here). Having foreign keys in both tables is not a good idea, as far as I can think. Can you explain why you think the last association is correct?
BTW, I think that the correct association is:
country has_many recipes and recipe belongs_to country

ActiveRecord associations, has_many_through

I've been trying to wrap my head around these associations and I've run into a bit of a snag. The app I'm working on has 2 models at the moment: Ingredient, and Recipe. The logic behind each is as follows:
The Ingredient model contains info on food items, such as name, bulk price, etc.
The Recipe model contains the name of the recipe, preparation instruction, and a list of ingredients (along with the amount required for the recipe).
After digging through some of the questions on here, and watching a few railscasts I've determined that a has_many_through relationship may be better for what I'm attempting to do, since I'd like to include additional info about the ingredient in the recipe (amount required).
Based on my limited experience in this area, I'd like to approach it like this:
class Recipe < ActiveRecord::Base
has_many :ingredients_recipes
has_many :ingredients, :through => :ingredients_recipes
end
class Ingredient_Recipe < ActiveRecord::Base
belongs_to :ingredient
belongs_to :recipe
end
class Ingredient < ActiveRecord::Base
has_many :ingredients_recipes
has_many :recipes, :through => :ingredients_recipes
end
Where the ingredients_recipes table would have fields similar to this: recipe_id, ingredient_id, ingredient_qty (for storing how much of a particular ingredient is in a recipe)
Is this the proper way to approach it code-wise, based on the functionality I mentioned above? Any help would be greatly appreciated. Thanks!
Edit
I'd also like to be able to add these new ingredients to the recipe from the recipe form. Would this require anything extra, like having to do accepts_nested_attributes_for the through table, or is this something that Rails would handle on it's own?
The :has_many through approach works best when one object can have zero or many other objects connected & there are some properties associated with each connection.
For example, in your case, you'd be storing the amount of each ingredient along with the recipe_id an ingredient_id in the Connections table.
So, it looks GOOD to me. :)
However, you should consider naming your Ingredient_Recipe table to something more appropriate.

Multiple Foreign Keys for a Single Record in Rails 3?

I am working on an app that will manage students enrolled in a course. The app will have users who can log in and manipulate students. Users can also comment on students. So three of our main classes are Student, User, and Comment. The problem is that I need to associate individual comments with both of the other models: User and Student. So I've started with some basic code like this...
class Student < ActiveRecord::Base
has_many :comments
end
class User < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :student
belongs_to :user
attr_accessible :comment
end
So in the comments table, a single record would have the following fields:
id
comment
student_id
user_id
created_at
updated_at
This presents several problems. First, the nice Rails syntax for creating associated objects breaks down. If I want to make a new comment, I have to choose between the foreign keys. So...
User.comments.create(attributes={})
OR
Student.comments.create(attributes={})
Another option is to arbitrarily pick one of the foreign keys, and manually add it to the attrs hash. So...
User.comments.create(:comment => "Lorem ipsum", :student_id => 1)
The problem with this option is that I have to list student_id under attr_accessible in my Comment model. But my understanding is that this poses a security risk since someone could technically come along and reassociate the comment with a different student using mass assignment.
This leads to a further question about data modeling in general using Rails. The app I'm currently building in Rails is one that I originally wrote in PHP/MySQL a few years ago. When I first studied SQL, great importance was placed on the idea of normalization. So, for example, if you have a contacts table which stores names and addresses, you would use a lot of foreign key relationships to avoid repeating data. If you have a state column, you wouldn't want to list the states directly. Otherwise you could potentially have thousands of rows that all contain string values like "Texas." Much better to have a separate states table and associate it with your contacts table using foreign key relationships. My understanding of good SQL theory was that any values which could be repeating should be separated into their own tables. Of course, in order to fully normalize the database, you would likely end up with quite a few foreign keys in the contacts table. (state_id, gender_id, etc.)
So how does one go about this in "the Rails way"?
For clarification (sorry, I know this is getting long) I have considered two other common approaches: "has_many :through =>" and polymorphic associations. As best I can tell, neither solves the above stated problem. Here's why:
"has_many :through =>" works fine in a case like a blog. So we have Comment, Article, and User models. Users have many Comments through Articles. (Such an example appears in Beginning Rails 3 from Apress. Great book, by the way.) The problem is that for this to work (if I'm not mistaken) each article has to belong to a specific user. In my case (where my Student model is here analogous to Article) no single user owns a student. So I can't say that a User has many comments through Students. There could be multiple users commenting on the same student.
Lastly, we have polymorphic associations. This works great for multiple foreign keys assuming that no one record needs to belong to more than one foreign class. In RailsCasts episode #154, Ryan Bates gives an example where comments could belong to articles OR photos OR events. But what if a single comment needs to belong more than one?
So in summary, I can make my User, Student, Comment scenario work by manually assigning one or both foreign keys, but this does not solve the issue of attr_accessible.
Thanks in advance for any advice!
I had your EXACT question when I started with rails. How to set two associations neatly in the create method while ensuring the association_ids are protected.
Wukerplank is right - you can't set the second association through mass assignment, but you can still assign the association_id directly in a new line.
This type of association assignment is very common and is littered throughout my code, since there are many situations where one object has more than one association.
Also, to be clear: Polymorphic associations and has_many :through will not solve your situation at all. You have two separate associations (the 'owner' of a comment and the 'subject' of a comment) - they can't be rationalised into one.
EDIT: Here's how you should do it:
#student = Student.find_by_id(params[:id])
#comment = #student.comments.build(params[:comment]) #First association is set here
#comment.user = current_user #Second association is set here
if #comment.save
# ...
else
# ...
end
By using the Object.associations.build, Rails automatically creates a new 'association' object and associates it with Object when you save it.
I think polymorphic association is the way to go. I'd recommend using a plugin instead of "rolling your own". I had great results with ActsAsCommentable (on Github).
As for your attr_accessible problem: You are right, it's more secure to do this. But it doesn't inhibit what you are trying to do.
I assume that you have something that holds the current user, in my example current_user
#student = Student.find(params[:id])
#comment = Comment.new(params[:comment]) # <= mass assignment
#comment.student = #student # <= no mass assignment
#comment.user = current_user # <= no mass assignment
if #comment.save
# ...
else
# ...
end
The attr_accessible protects you from somebody sneaking a params[:comment][:student_id] in, but it won't prevent the attribute from being set another way.
You still can get all comments of your users through the has_many :comments association, but you can also display who commented on a student thanks to the belongs_to :user association:
<h1><%= #student.name %></h1>
<h2>Comments</h2>
<%- #student.comments.each do |comment| -%>
<p><%= comment.text %><br />
by <%= comment.user.name %></p>
<%- end -%>
PLUS:
Don't over engineer your app. Having a state:string field is perfectly fine unless you want to do something meaningful with a State object, like storing all districts and counties. But if all you need to know a students state, a text field is perfectly fine. This is also true for gender and such.
Well, for the first part. If I understood your question correctly, I think, that since comments are listed within students controller, you should associate them through Student model (it just seems logical to me). However, to protect it from assigning wrong user id, you could do something like this
#student = Student.find params[:id]
#student.comments.create :user => current_user
current_user might be a helper that does User.find session[:user_id] or something like that.
has_many :through association doesn't make sense here. You could use it to associate User and Student through Comment, but not User and Comment through Student
Hope that helps.

Should a one-to-one relation, column, or something else be used?

Though my problem is specifically in Ruby on Rails, I'm also asking generally: I have a users table and want to designate some users to be students and others to be teachers. What is the best way to design the schema in this case?
Edit:
Though I originally accepted vonconrad's answer--it fits the original criteria--I have to reopen and adjust the question based on feedback from nhnb.
What should be done if students and/or teachers require additional (unique) attributes?
This greatly depends on how many the different attributes between the teacher and student there'll be as well as how often new unique attributes will be added or removed. If the case is that they will differ a lot, you have two choses:
Make two models, one for Student and one for Teacher and use ruby include to share any logic between the two models. Any associations that associate with a user, you would use a polymorphic association (has_many :user, :polymorphic => true)
Put the attributes in another table. For example: you'll have the users table and a user_attributes table. users would have id, user_type, username, etc. and user_attributes would have id, user_id, attribute_name, value.
If, however, your different attributes between the two users are few and pretty rock solid, you should just consider using Single Table Inheritance.
Good luck!
In this case, I'd simply go with a single boolean column called teacher. If true, the user is a teacher. If false, it's a student. No need to create another model.
If you want more than two roles (say, student, teacher, administrator, janitor(?)), you can add another model called UserGroup. You'll also have to create a column in the users table called user_group_id. In the new model, you have a single row for each of the roles. Then, you specify the following relationships:
class User < ActiveRecord::Base
belongs_to :user_group
end
class UserGroup < ActiveRecord::Base
has_many :users
end
This will allow you to assign each user to a specific group.