Data modeling of Grandparent, Parent, and Child relationships in Rails - ruby-on-rails

Is it a bad practice to set a model (table) association between both parent and child AND grandparent and child? For example, if I want to easily query a user's projects or a user's tasks, is the following setup recommended? If so, is there a good way to ensure both foreign keys always point to the same user?
Any help will be greatly appreciated.
class User < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name
has_many :projects
has_many :tasks
end
class Project < ActiveRecord::Base
attr_accessible :name, :status
belongs_to :user
has_many :tasks
end
class Task < ActiveRecord::Base
attr_accessible :name, :status
belongs_to :user
belongs_to :project
end

There is nothing wrong with what you are trying to do, in fact rails has already predefined some association methods to help you. Just a couple of modifications and you are good to go
class User < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name
has_many :projects
has_many :tasks, through: :projects
end
By adding the through: :projects to your tasks now means you can access all of a users tasks like so
$user = User.first #or whatever
$user.tasks
=> [AR array of all tasks]
You can play around with it in irb. In addition you don't need belongs_to :user in your Task model. It's already taken care of.
Look at section 2.4 for more details
EDIT: I have made the assumption (based upon your description) that a task belongs to a project, and a project belongs_to a user, and that user's don't have tasks directly, but through projects. If that was wrong, let me know and we'll figure it out from there.

Related

Ruby on Rails: Possible/preferrable to implement single table inheritance after creating separate models?

I've built a simple address book application in my intro Ruby on Rails class with separate models for street addresses (Address), email addresses (Email), and web addresses (Web) using nested forms using a single controller (Entry). I'd like to now change the last two models (Email and Web) to use single table inheritance from a base Url table. Is that preferable (or even possible) compared to rebuilding the app from scratch with the correct inheritance relationships?
I've included my existing models below:
class Entry < ActiveRecord::Base
attr_accessible :first_name, :last_name, :addresses_attributes, :webs_attributes, :emails_attributes
has_many :addresses, dependent: :destroy
has_many :emails, dependent: :destroy
has_many :webs, dependent: :destroy
accepts_nested_attributes_for :addresses, :emails, :webs, allow_destroy: true, reject_if: :all_blank
end
class Address < ActiveRecord::Base
belongs_to :entry
belongs_to :address_type
attr_accessible :address_type_id, :city, :state, :street, :zip
end
class Email < ActiveRecord::Base
belongs_to :entry
belongs_to :address_type
attr_accessible :address_type_id, :email, :entry_id
validates_email_format_of :email
end
class Web < ActiveRecord::Base
belongs_to :entry
belongs_to :address_type
attr_accessible :address_type_id, :web, :entry_id
end
How would changing
class Email < ActiveRecord::Base
and
class Web < ActiveRecord::Base
to
class Email < Url
and
class Web < Url
affect my existing application?
Thanks in advance for your help and advice.
Be sure to also add the Url class that's inheriting the ActiveRecord::Base class.
class Url < ActiveRecord::Base
belongs_to :entry
belongs_to :address_type
attr_accessible :address_type_id :entry_id
end
class Email < Url
attr_accessible :email
validates_email_format_of :email
end
class Web < Url
attr_accessible :web
end
also add the extra line to your entry.rb:
has_many :urls, dependent: :destroy
It is possible to generate a migration that sets up the single table inheritance, but I was unfortunately not able to do this successfully without breaking other things in my app. I went ahead and restarted fresh with a new application and correctly implemented the proper inheritance. This was in the interests of time and the practice of building an application from scratch. In a real-world environment, I'm sure it would be worth investing the time to create the proper migration and changes to the various dependencies.
Thanks Zippie for your advice. I appreciate it.

ActiveRecord won't build the right class using STI

I'm using single table inheritance in my application and running into problems building inherited users from an ancestor. For instance, with the following setup:
class School < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
attr_accessible :type #etc...
belongs_to :school
end
Class Instructor < User
attr_accessible :terms_of_service
validates :terms_of_service, :acceptance => true
end
Class Student < User
end
How can I build either a instructor or student record from an instance of School? Attempting something like School.first.instructors.build(....) gives me a new User instance only and I won't have access to instructor specific fields such as terms_of_service causing errors later down the rode when generating instructor-specific forms, building from console will give me an mass-assignment error (as it's trying to create a User record rather than an Instructor record as specified). I gave the example of School, but there are a few other associations that I would like to inherit from the User table so I don't have to repeat code or fields in the database. Am I having this problem because associations can not be shared in an STI setup?
You should specify instructors explicitly
class School < ActiveRecord::Base
has_many :users
has_many :instructors,:class_name => 'Instructor', :foreign_key => 'user_id'
end
And what else:
class School < ActiveRecord::Base
has_many :users
has_many :instructors
end
class Instructor < User
attr_accessible :terms_of_service # let it be at the first place. :)
validates :terms_of_service, :acceptance => true
end
OK it seems part of the problem stemmed from having the old users association inside of my School model. Removing that and adding the associations for students and instructors individually worked.
Updated School.rb:
class School < ActiveRecord::Base
#removed:
#has_many :users this line was causing problems
#added
has_many :instructors
has_many :students
end

Views for many to many associations [ Rails ]

I am newbie to Ruby on Rails and trying to learn this hard way , by writing my own project.
I have scenario where a test contain many problems, and problems can be reused in other tests also. So I have created models something like this.
class Test < ActiveRecord::Base
has_many :test_problems
has_and_belongs_to_many :problems
has_many :problems, :through => :test_problems
belongs_to :user
attr_accessible :name, :user_id, :reusable
end
class Problem < ActiveRecord::Base
belongs_to :user
has_many :test_problems
has_and_belongs_to_many :tests
has_many :tests, :through => :test_problems
attr_accessible :name, :statement, :input, :output, :user_id , :reusable
end
class TestProblem < ActiveRecord::Base
belongs_to :test
belongs_to :problem
attr_accessible :problem_id, :test_id
end
Now I have tested I can correctly add the association in Test Controller create after save.
#test.problems << Problem.find( :last )
I am looking for way how can user add many problems to his test when he create new one. I am unable to write the view and controller code handling this. What javascript are required. First a solution is good and then how can proceed to optimize this using partial extra would be great.
-Hemant

Rails: Connecting Model to another Model

I just created new columns in my database for my micropost table and these columns were vote_count comment_count and I want to connect it to the Vote models vote_up count and the Comment models comment count. Since I just added these columns although there were votes and comments, how do I connect these other models to the micropost model to fill in the new columns. Any suggestions are much appreciated!
Micropost Model
class Micropost < ActiveRecord::Base
attr_accessible :title, :content, :view_count
acts_as_voteable
belongs_to :school
belongs_to :user
has_many :comments
has_many :views
accepts_nested_attributes_for :comments
end
It looks like what you're trying to do is use a counter_cache, which rails supports, but you've got the names of the columns wrong.
You want to add a comments_count and a votes_count column to your database instead of the ones that you have.
Then you can hook it up to your models as follows:
class Micropost< ActiveRecord::Base
attr_accessible :title, :content, :view_count
acts_as_voteable
belongs_to :school
belongs_to :user
has_many :comments, :counter_cache => true
has_many :views
accepts_nested_attributes_for :comments
end
The votes half of it is a bit more tricky since you're using some extra code with your acts_as_votable module, but counter caches are the way that you want to go if I understand you correctly.
Here is more info on them: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

How to establish associations for a model with two belongs_to relationships?

I am building an application with the following model functions
Groups have many Users
Groups have many Expenses (each expense has a :name, :total, :added_by_user_id fields)
Expenses have many owings (1 for each user in the group)
Owings have an :amount and a :user_id, to reference which user the owing is referring
So far, I have set up the models as followings:
# user.rb
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email, :password
has_many :memberships, :foreign_key => "member_id", :dependent => :destroy
has_many :groups, :through => :memberships
has_many :owings
end
# group.rb
class Group < ActiveRecord::Base
attr_accessible :name
has_many :memberships, :dependent => :destroy
has_many :members, :through => :memberships
has_many :expenses
end
# expense.rb
class Expense < ActiveRecord::Base
attr_accessible :total_dollars, :name, :owings_attributes, :added_by_user_id
belongs_to :group, :inverse_of => :expense
has_many :owings, :dependent => :destroy
end
# owing.rb
class Owing < ActiveRecord::Base
attr_accessible :amount_dollars, :user_id
belongs_to :expense, :inverse_of => :owings
belongs_to :user, :inverse_of => :owings
end
# NB - have left off memberships class (and some attributes) for simplicity
To create an expense, I'm using #group.expenses.build(params[:expenses]), where params come from a nested model form that includes attributes for the owings that need to be created. The params include the 'user_id' for each of the 'owing' instances for that expense.
I have two concerns:
Firstly - I've made 'user_id' accessible in the owings model, meaning that a malicious user can change who owes what in an expense (I think?). I don't know how to get around this, though, because the user needs to see the names of all the other members of the group when they fill out the expense/owings form.
Secondly - I've also made 'added_by_user_id' accessible in the expense model - I also wouldn't want malicious users to be able to change this, since this user_id has special edit/delete priveleges for the expense. Is there some clever way to make an expense 'belong_to' a User AND a group, and set both of these associations when creating WITHOUT having to make either an accessible attribute? If it helps, the 'added_by_user_id' can always be set to the current_user.
Any ideas? Very possible I'm missing something fairly fundamental here.
Thanks in advance!
PS. Long time listener, first time caller. Thanks to all of you for teaching me ruby on rails to date; this website is an incredible resource!
Have you thought about setting them dynamically?
dynamic attr-accessible railscast

Resources