Is there a better way to structure these database tables? - sql

I'm struggling with the best way to design/query my db on a new Rails app. This is what I have in place right now:
documents:
title
has_many :document_sections
document_sections:
belongs_to :document
habtm :resources
resources_document_sections:
belongs_to :resource
belongs_to :document_section
resources:
text
So it's easy to say document_section.resources. But document.resources is giving me trouble
The only way I've found to do it so far is to collect the document section ids, and then run a second query:
d = Document.last
s_ids = d.document_section_ids
Resource.joins(:document_sections)
.where(document_sections: { id: s_ids })
.uniq
So this starts bad, and gets worse as the queries get more complicated. It's becoming quite a headache every time I have to touch this relationship.
I'm wondering if there is a different pattern that I could follow in laying out these tables, such that querying against them is not such a headache? Or is there a better querying strategy that I'm missing?

Your document has no relationships. You need to add something to both models in a relationship, you can't just add it to document_sections and expect documents to have any kind of relationship to anything.
You need to add a has_many ... through: to your Document:
class Document < ActiveRecord::Base
has_many :document_sections
has_many :relationships, through: :document_sections
end

Related

rails multiple has_many relationships between the same models

I'm trying to figure out the best way to model a many-to-one relationship in rails where there are multiple scopes to the relationship.
An example would be a restaurant has-many photos. I want to be able to call
restaurant.lounge_photos
and receive only the lounge photos,
but also be able to call
restaurant.food_photos
and receive just the food photos.
The two methods I can think of are:
to use multiple joins table, and a has_many to has_one relationship for each type of photo.
to add a 'type' attribute to the photo model and write a scoping method.
Both of these seem a bit clunky to me.
Is there a better solution here?
I think you have to go has_many and Single Table Inheritance(STI), as follow.
Make association with restaurant and photo
class Restaurant < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :restaurant
end
Then you have to use STI in Photo model. The reason is that you have almost all fields are common for lounge_photos and food_photos.
OR
Using scope directly you can differentiate it and achieve your goal.
For more details of use STI you can refer this link.
This is one way, using a type column
has_many :food_photos,
class_name: 'Photo',
foreign_key: :restaurant_id,
-> { where(type: 'food') }
has_many :lounge_photos,
class_name: 'Photo',
foreign_key: :restaurant_id,
-> { where(type: 'lounge') }

has_many association ignores restricting condition

I'm working on a Rails 3.0.x application (actually it's Hobo 1.3.x but that's not material to this question). Among the models, there are GraphPanes, GraphLabels, and LabelSets. A GraphPane can have GraphLabels and LabelSets. GraphLabels can belong to GraphPanes or LabelSets, but not both. So if a GraphLabel belongs to a LabelSet, I'd like to keep it from being associated to a GraphPane.
I am trying to enforce that with this code in the GraphPane model:
has_many :graph_labels, :conditions => 'label_set_id = NULL'
However, I'm still able to associate GraphLabels with not-null label_set_id with GraphPanes. Why? How can I stop this?
This question is superficially similar, but my relationship isn't polymorphic, so the nominal solution there doesn't help me.
The functionality of :conditions on has_many is to filter the results that are passed back via the graph_labels, not to protect objects from being added to the association.
If you add a graph_label with no label_set_id, the association will build, but if you then ask for graph_pane.graph_labels, it will not return that non-condition-matching graph_label.
The has_many/belongs_to relationship is saved on the belongs_to model, graph_label, and so the parent/has_many/graph_pane does not stop the graph_label from writing whatever it wants to its graph_pane_id attribute. This delegation of responsibility is correct, although frustrating, I agree.
Now, as for how to stop this, I'm not sure. It sounds like you need some sort of validation on the graph_label object, something along the lines of not allowing a graph_pane_id to be set on a graph_label if that graph_label's label_set_id is nil. Since the has_many/belongs_to relationship is saved on the graph_label, you should write the validation on the graph_label. That way, the graph_label will not be able to be saved with a new graph_panel_id unless it fulfills the condition.
Thoughts? Questions?
Reference:
has_many
Alternate Solution
I've reread your question and I think want you want here is a polymorphic association.
def GraphPane < ActiveRecord::Base
has_many :label_sets
has_many :graph_labels, as: :parent
end
def LabelSet < ActiveRecord::Base
belongs_to :graph_pane
has_many :graph_labels, as: :parent
end
def GraphLabel < ActiveRecord::Base
belongs_to :parent, polymorphic: true
end
That way, a GraphLabel can only have a single parent, which is what your “spec” above requires. Is there any reason not to implement the relations in this way?

Returning associations for specific model when there is a polymorphic association in Rails 3.2

I have a polymorphic association in a Rails 3 app where a User may favorite objects of various classes.
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :favoriteable, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :favorites
end
class Image < ActiveRecord::Base
has_many :favorites, :as => :favoriteable
end
class Video < ActiveRecord::Base
has_many :favorites, :as => :favoriteable
end
I would like to be able return a list of just a User's favorite_images for example.
user.favorite_images #returns a list of the images associated with the user via :favoritable
I'm guessing there is a straightforward way of doing this but I haven't been able to figure it out. Let me know if you need anymore info.
Thanks!
===edit====
I know that I could retrieve what I am looking for via
favorite_images = user.favorites.collect{|f| if f.favoriteable_type=='Image'; f.favoriteable; end}
I could define an instance method for the User class and put that inside. I was wondering if there is a way to do it as some sort of has_many association. Really just because going forward it would be easier to have all that in one place.
When you created the table for Favorite you created a couple of columns favoriteable_id and favoriteable_type and you can use this information to restrict your query.
If you do user.favorites you will get all of the favorites and to restrict them to say just the images then you can do user.favorites.where(favoriteable_type: 'image') but that just gives you the favorite records and it sounds like you want the actual images. To get those you can do it by then mapping and pulling the favoriteable out. You'll likely want to include it in the query though so you don't hit the database so much. I would also make this a method on User.
def favorite_images
favorites.includes(:favoriteable).where(favoriteable_type: 'Image').map(&:favoriteable)
end

Retrieving sublist 3 level deep in Rails

I have a datamodel that contains a Project, which contains a list of Suggestions, and each Suggestion is created by a User. Is there a way that I can create a list of all distinct Users that made Suggestions within a Project?
I'm using Mongoid 3. I was thinking something like this, but it doesn't work:
#project = Project.find(params[:id])
#users = Array.new
#users.push(#project.suggestions.user) <-- this doesn't work
Any ideas? Here's my model structure:
class Project
include Mongoid::Document
has_many :suggestions, :dependent => :destroy
...
end
class Suggestion
include Mongoid::Document
belongs_to :author, class_name: "User", :inverse_of => :suggestions
belongs_to :project
...
end
class User
include Mongoid::Document
has_many :suggestions, :inverse_of => :author
...
end
While Mongoid can give MongoDB the semblance of relationships, and MongoDB can hold foreign key fields, there's no underlying support for these relationships. Here are a few options that might help you get the solution you were looking for:
Option 1: Denormalize the data relevant to your patterns of access
In other words, duplicate some of the data to help you make your frequent types of queries efficient. You could do this in one of a few ways.
One way would be to add a new array field to User perhaps called suggested_project_ids. You could alternatively add a new array field to Project called suggesting_user_ids. In either case, you would have to make sure you update this array of ObjectIds whenever a Suggestion is made. MongoDB makes this easier with $addToSet. Querying from Mongoid then looks something like this:
User.where(suggested_project_ids: some_project_id)
Option 2: Denormalize the data (similar to Option 1), but let Mongoid manage the relationships
class Project
has_and_belongs_to_many :suggesting_users, class_name: "User", inverse_of: :suggested_projects
end
class User
has_and_belongs_to_many :suggested_projects, class_name: "Project", inverse_of: :suggesting_users
end
From here, you would still need to manage the addition of suggesting users to the projects when new suggestions are made, but you can do so with the objects themselves. Mongoid will handle the set logic under the hood. Afterwards, finding the unique set of users making suggestions on projects looks like this:
some_project.suggesting_users
Option 3: Perform two queries to get your result
Depending on the number of users that make suggestions on each project, you might be able to get away without performing any denormalization, but instead just make two queries.
First, get the list of user ids that made suggestions on a project.
author_ids = some_project.suggestions.map(&:author_id)
users = User.find(author_ids)
In your Project class add this :
has_many :users, :through => :suggestions
You'll then be able to do :
#users.push(#project.users)
More info on :through here :
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
For mongoid, take a look at this answer :
How to implement has_many :through relationships with Mongoid and mongodb?

Has_many :through association

I made a relationship with the three models using has_many :through:
class Curriculum class < ActiveRecord::Base
has_many :interests
has_many :vacancies,: through => :interests
end
class Vacancy class < ActiveRecord::Base
has_many :interests
has_many :resumes,: through => :interests
end
class Interest < ActiveRecord:: Base
belongs_to :vacancy
belongs_to :curriculum
end
And to create curriculum and vacancy, I create them by administrative, i need to know how can i create the interest to the id of the vacancy, and how it will be logged on the system I have to get the id of it and make the relationship in creating a new bank interest. I wonder how I can program it to do so, and I wonder how the controller will get the create action, and what better way to do this.
First, try to read the whole "Guide to Rails on Associations", especially the part about has_many :through. Then check your schema if your db is migrated and contains for the table interests the necessary foreign keys to curriculums and vacancies called curriculum_id and vacancy_id.
If that is all in place, the following code will create the relationship between two objects:
#curr = Curriculum.find(1)
#vac = Vacancy.find(1)
#curr.interests << #vac
#curr.save
The last two lines creates an interest between #curr and #vac and store that on the database. So you should not use IDs and handle them directly, but work with objects instead.
The second part now is to provide a UI to allow the definition (and removal) of interests between curricula and vacancies. The base flow here is:
You have one curriculum in focus.
You have a link to add / remove curricula.
The view that opens shows a list of possible vacancies, where every vacancy has a checkbox.
By selecting (or deselecting) the check boxes, the IDs of the vacancies will be held in the params of the request sent to the controller.
See the (older) podcast Railscast #52 how to do that in a similar context. Or see the example for has_many :through with checkboxes.
An alternative way would be to use JQuery autocomplete, and add so interests one-by-one. See the nice podcast Railscast #258 which uses JQuery Tokeninput for that.
I think this is what your looking for:
HABTM Checkboxes
That's the best way to use an Has and Belongs to many association.