Trying to figure out what kind of relationships I should use - ruby

Sinatra, Mongoid 3
There 4 models: User, Book, FavoriteBooks, ReadBooks, NewBooks. Each user has their list of the favourites, read and new books. A book belongs to a list. But it's also possible to request an information about any book which means books should not be embedded into FavoriteBooks, ReadBooks, NewBooks.
The part of the scheme:
class Book
include Mongoid::Document
belongs_to :favourite_books
belongs_to :read_books
belongs_to :new_books
end
class FavoriteBook
include Mongoid::Document
has_many :books
end
#.... the same for ReadBooks and NewBooks
class User
include Mongoid::Document
# what else?
end
It seems like I missed something.
What should I do to make a user "contain" the lists of FavoriteBooks, ReadBooks, NewBooks? Should I use one-to-one relationship?

I think you should rethink your modeling. IMHO it should be book and user as models, and the favorite_books, read_books and new_books should all be relationhips like so:
class User
include Mongoid::Document
has_many :favorite_books
has_many :read_books
has_many :new_books
has_many :books, :through => :favorite_books
has_many :books, :through => :read_books
has_many :books, :through => :new_books
end
class Book
include Mongoid::Document
has_many :favorite_books
has_many :read_books
has_many :new_books
has_many :users, :through => :favorite_books
has_many :users, :through => :read_books
has_many :users, :through => :new_books
end
class FavoriteBook
include Mongoid::Document
belongs_to :books
belongs_to :users
end
#.... the same for ReadBooks and NewBooks
I think this should be a better approach. =)

Related

Mongoid: Referencing Same Model More Than Once Through has_many

I'd like to be able to reference a model (a has_many relationship) more than once in the same model. For example, given the following models:
class MyModel
include Mongoid::Document
field :name, type: String
has_many :main_efforts, :class_name => 'Effort', as: :effortable, dependent: :delete, autosave: true
has_many :secondary_efforts, :class_name => 'Effort', as: :effortable, dependent: :delete, autosave: true
validates_presence_of :name
end
class Effort
include Mongoid::Document
field :name, type: String
belongs_to :effortable, polymorphic: true
validates_presence_of :name
end
As you can see, the Effort model is referenced twice. Originally, my Effort model wasn't polymorphic, but it seemed Mongoid was unable to determine which collection (main_efforts or secondary_efforts) the effort belonged to. As such, I made it polymorphic. After making it polymorphic, however, my main_efforts and secondary_efforts fields are always an empty array.
What is the proper way to reference a polymorphic model more than once in the same model (assuming a polymorphic model is necessary)?
Figured it out:
class MyModel
include Mongoid::Document
field :name, type: String
has_many :main_efforts, :class_name => 'Effort', dependent: :delete, autosave: true, :inverse_of => :main_effort
has_many :secondary_efforts, :class_name => 'Effort', dependent: :delete, autosave: true, :inverse_of => :secondary_effort
validates_presence_of :name
end
class Effort
include Mongoid::Document
field :name, type: String
belongs_to :main_effort, :class_name => 'Conop', :inverse_of => :main_efforts
belongs_to :secondary_effort, :class_name => 'Conop', :inverse_of => :secondary_efforts
validates_presence_of :name
end

Rails ActiveRecord query for most recent viewed resources

I recently made the following models:
class User < ActiveRecord::Base
has_many :resources
has_many :resource_views, :through => :user_resource_views, :source => 'Resource'
end
class Resource < ActiveRecord::Base
belongs_to :user
has_many :resource_views, :through => :user_resource_views, :source => 'Resource'
end
class UserResourceView < ActiveRecord::Base
attr_accessible :resource_id, :user_id
belongs_to :resource
belongs_to :user_id
end
Now, I want my home#index action to set #resources to the current_user's most recently viewed resources. How would you advise I proceed? Perhaps something similar to current_user.resource_views.find(:all, :order => 'created_at')?
In SQL, I would do something like:
SELECT *
FROM Resource
WHERE id IN (SELECT * FROM UserResourceView WHERE user_id = current_user.id)
... but then the ORDER BY created_at, hmmm
I'll be periodically adding progress updates throughout the day until I figure it out.
Given you're on Rails 3.x, what you're looking for is probably something like this:
class User < ActiveRecord::Base
has_many :resources
has_many :resource_views, :class_name => 'UserResourceView'
has_many :viewed_resources, :through => :resource_views, :source => :resource
def recently_viewed_resources
viewed_resources.order('user_resource_views.created_at DESC')
end
end
class Resource < ActiveRecord::Base
belongs_to :user
has_many :resource_views, :class_name => 'UserResourceView'
end
class UserResourceView < ActiveRecord::Base
attr_accessible :resource_id, :user_id
belongs_to :resource
belongs_to :user_id
end
And to access the collection in your controller:
current_user.recently_viewed_resources

Mongoid not saving nested_attributes for 'belongs_to' in a 1:1 relationship

I have two model's Contact, and User. When I create a new user I am trying to create the contact at the same time. But It is not getting created for some reason. Any ideas on why?
class Contact
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::MultiParameterAttributes
include Mongoid::Paranoia
include Mongoid::Versioning
# Attr.
attr_accessible :first_name, :last_name, :birthday, :email_addresses_attributes, :phone_numbers_attributes, :relationships_attributes, :addresses_attributes
#Relationships
belongs_to :firm, validate: true
has_one :user # contact information for user
has_many :relationships, autosave: true
has_many :clients
has_many :notes, dependent: :destroy
...
end
class User
include Mongoid::Document
include ActiveModel::SecurePassword
include Mongoid::Timestamps
# Attr.
attr_accessible :contact_id, :contact_attributes, :password, :password_confirmation, :google_tokens
#Relationships
belongs_to :firm, validate: true
belongs_to :contact, validate: true, autosave: true
has_one :user_type
embeds_many :histories
# Nested Attrs
accepts_nested_attributes_for :contact
...
end
accepts_nested_attributes_for is done on the owner object to allow you to set attributes of objects that belong it.
In your case, User belongs to (or is nested under) Contact. You would have to do accepts_nested_attributes_for :user in your Contact model.
You could switch it around so that User has_one :contact and Contact belongs_to :user. This requires you give Contact a user_id field.

Active Record Relation Joins - 3 main tables, 2 join tables

The setup
I have a data model with 3 major tables (users, links, topics) with 2 join tables (link_saves and link_topics). My models:
User
has_many :link_saves, :class_name => 'LinkSave', :foreign_key => 'user_id'
has_many :links, :through => :link_saves
LinkSave
belongs_to :user
belongs_to :link
Link
has_many :link_saves, :class_name => 'LinkSave', :foreign_key => 'link_id'
has_many :users, :through => :link_saves
has_many :link_topics, :inverse_of => :link
has_many :topics, :through => :link_topics
LinkTopic
belongs_to :link
belongs_to :topic
Topic
has_many :link_topics
has_many :links, :through => :link_topics
The Question
I want to be able to find a list of all the topics a user has saved links for. I would like to be able to do #user.topics and have it hop across all 5 tables from user all the way to topics. More importantly, I want this to return an ActiveRecord relation so that I can scope/sort/page the list of user topics further so this would NOT work:
## app/models/user.rb
def topics
links.collect(&:topics)
end
Am I going down the wrong path? Is there a way to do this through active record without having to write all the custom SQL? Help please!
Possible Answers (Update)
Using multiple has_many :throughs to make all the hops. This works, but can't be best practice, right?
## app/models/user.rb
has_many :link_saves, :class_name => 'LinkSave', :foreign_key => 'user_id'
has_many :links, :through => :link_saves
has_many :link_topics, :through => :links, :uniq => true
has_many :topics, :through => :link_topics, :uniq => true
I think this is called a 'nested' has_many through, basically going from A to B to C.
In Rails 3.1 this functionality is now supported
http://www.warmroom.com/yesterdays/2011/08/30/rails-3-1-nested-associations/
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html (search for 'Nested')
The example they have is a bit simpler than what you have, but I think it should be enough for you to get some ideas.
class Author < ActiveRecord::Base
has_many :posts
has_many :comments, :through => :posts
has_many :commenters, :through => :comments
end
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :commenter
end
#author = Author.first
#author.commenters # => People who commented on posts written by the author
Prior to Rails 3.1 there was a plugin
'https://github.com/releod/nested_has_many_through'

has_many :through child and parent validations

I have a rails 3.0
has_many :X, :through => :something set up and i have a custom validator that does some custom validation on some complicated logic. I want to when you add anything to this many to many relationship both models are valid?
Project Class:
class Project < ActiveRecord::Base
has_many :assignments
has_many :users, :through => :assignments
validates :name, :presence => true
end
Assignment:
class Assignment < ActiveRecord::Base
belongs_to :project
belongs_to :user
end
User class with custom validator:
class MyCustomValidator < ActiveModel::Validator
def validate( record )
if record.projects.length > 3
record.errors[:over_worked] = "you have to many projects!"
end
end
end
class User < ActiveRecord::Base
has_many :assignments
has_many :projects, :through => :assignments
validates :name, :presence => true
validates_with MyCustomValidator
end
What i really want to do is prevent each model from invalidating the other so to say
prevent
my_user.projects << fourth_project
and
my_project.users << user_with_four_projects_already
from happening. Right now it allows the assignment and just the user becomes invalid.
class Project < ActiveRecord::Base
has_many :assignments
has_many :users, :through => :assignments
validates :name, :presence => true
validates_associated :users
end
According to the docs, users must already be assigned to Projects in order to be validated. So:
my_user.projects << fourth_project
would occur, then projects would validate the user and see that it is indeed invalid, making the project invalid as well as in the inverse example.

Resources