How to do a self referential model in Mongoid? - ruby-on-rails

I recently posted this question: How to model a many self-referential relationship with many parents?
I got an adequate answer, but I was told that I could model this better using Mongo/Mongoid. I always wanted to attempt using a different database paradigm so maybe this will be a good start.
Is it true that this would be easier to model using Mongo? If so, could you help lay it out for me?
I basically have two options right? 1 is to create an array through a document that references Skill id's through it? E.g, Skill.prerequisites = [Skill1, Skill2, Skill3]. Something that works with that structure right?
Otherwise it may be that I need to run something logical on that association so I'd have to create a separate model. This is where I get confused. Given I may need to create a separate model, does that exist as a document or an embedded document? What are my limitations w/ each strategy?

OK, so let's go with your skill approach. Here's how I'd model it in Mongoid:
class Skill
include Mongoid::Document
field :prereqruisite_skill_ids, type: Array, default: []
def prereqruisite_skills
Skill.any_of(:_id => prereqruisite_skill_ids).all
end
def add_prereqruisite!(skill)
self.prereqruisite_skill_ids << skill
safely.save!
end
end
With Mongo, this is the only way I would do it.
With ActiveRecord, you could:
class Skill < AR::Base
has_many :skill_prereqruisites
has_many :prerequisites, through: skill_prereqruisites, class_name: "Skill"
end
class SkillPrereqruisite < AR::Base
belongs_to :skill_one, foreign_key: "skill_one_id"
belongs_to :skill_two, foreign_key: "skill_two_id"
end

Related

Rails field on join entity in has_many through relationship

I may be going about this the wrong way but after reading various SO articles and the Rails docs on associations and scopes, I'm not much wiser.
I have a many-to-may relationship expressed like so:
class User < ActiveRecord::Base
has_many :user_program_records
has_many :programs, through: :user_program_records
end
class Program < ActiveRecord::Base
has_many :user_program_records
has_many :users, through: :user_program_records
end
class UserProgramRecord < ActiveRecord::Base
belongs_to :user
belongs_to :program
# has a field "role"
end
The idea is that there are many users in the system and many programs. Programs have many users in them and users may belong to multiple programs. However - within a given program, a user can only have one role.
What I'd really like to be able to write is:
Program.first.users.first.role
and have that return me the role (which is just a String).
What's the cleanest way to do this? Basically, once I've scoped a user to a given program, how do I cleanly access fields on the relevant join table?
You are thinking about it slightly wrong:
user.role
Would be very ambiguous as a user can have different roles in different programs. Instead you need to think of the join entity as a thing of its own.
The easiest way is to select the join model directly:
program = Program.includes(:user_program_records, :users).first
role = program.user_program_records
.find_by(user: program.users.first)
.role
You can use stuff like association extensions and helper methods to make this a bit sexier.

Rails 2 tables, 1 model

I am relatively new to ruby/rails and I have the following question:
I am working on a scheduling app and have a model named Classes and another named ClassEntries. The relationship between them is that each user can have multiple class entries per semester, each relating to one class. Each record in the Classes table belongs to a specific University. A User can have multiple entries in the ClassEntries table for 1 semester (typically 5). Their schedule is comprised of all their ClassEntries with the same semester ID.
I am not sure whether I should have a third model called Schedule that brings together the info in the ClassEntries and Classes models for the user at hand. I originally wrote this functionality in PHP and I simply used a MySQL JOIN to gather the necessary information. In Rails it seems that there should be a better way to accomplish this.
What would be the best way of going about this in Rails?
Many thanks
So, what you are looking for is pretty much associations in Rails.
You would have the following:
def User < ActiveRecord::Base
has_many :course_entries
has_many :courses, :through => :class_entries
end
def CourseEntry < ActiveRecord::Base
belongs_to :user
belongs_to :course
end
def Course < ActiveRecord::Base
has_many :course_entries
has_many :users, :through => :class_entries
end
With those associations set up, Rails would allow you to do such things like
some_user.courses or some_course.users and it will make the joins through CourseEntry for you.
Let me know if this helps. If you need me to go more in depth let me know.

Best practice about empty belongs_to association

Imagine the following situation:
I have a dog model and a house model. A dog can belong to a house, and a house can have many dogs, so:
Class Dog < ActiveRecord::Base
belongs_to :house
end
Class House < ActiveRecord::Base
has_many :dogs
end
Now, imagine that I also want to create dogs that don't have a house. They don't belong to house. Can I still use that relationship structure and simply don't inform a :house_id when creating it?
Is there a better practice?
Obs.: I used this analogy to simplify my problem, but my real situation is: I have a model a user can generate instances of it. He can also create collections of those instances, but he can leave an instance outside a collection.
Be careful with this in Rails 5...
#belongs_to is required by default
From now on every Rails application will have a new configuration
option config.active_record.belongs_to_required_by_default = true, it
will trigger a validation error when trying to save a model where
belongs_to associations are not present.
config.active_record.belongs_to_required_by_default can be changed to
false and with this keep old Rails behavior or we can disable this
validation on each belongs_to definition, just passing an additional
option optional: true as follows:
class Book < ActiveRecord::Base
belongs_to :author, optional: true
end
from: https://sipsandbits.com/2015/09/21/whats-new-in-rails-5/#belongs_toisrequiredbydefault
I think it is absolutely normal approach.
You can just leave house_id with null value in database for the models which don't belong to other.

How can I use Mongoid and ActiveRecord in parallel in Rails 3?

I'm using rails 3, and began my application with ActiveRecord. Now, I have many models, and the relations are starting to get complicated, and some could be more simply expressed with a Document-Oriented structure, so I'd like to try migrating to MongoDB and use Mongoid.
I've always heard that you didn't have to eitheer use all MongoDB or nothing, but that you could use the two in parallel while migrating. I don't see how to go about this from the docs though.
For example, I have:
class User < ActiveRecord::Base
has_many :items
has_many :products, :through => :items
end
class Product < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :user
belongs_to :product
# alot of data that fits a hierarchical document-oriented structure
end
I'd like to ideally begin by replacing my Item activerecord model with a Mongoid document, so my items are stored in MongoDB, and my Users and Products can stay in my SQL DB
Thing is, I don't see how to do this. Am I going about this the right way?
Perhaps another alternative is to keep a base AR Item
class Item < ActiveRecord::Base
has_one :mongodb_item ?? # I know this is wrong
end
class MongodbItem
include Mongoid::Document
belongs_to AR_Item ??? # I know this is also wrong
end
Thanks!
What I did was just mock the relationship with methods in each the AR model and the Mongoid model like so.
# visit_session.rb
class VisitSession
include Mongoid::Document
include Mongoid::Timestamps
field :user_id, type: Integer
index({user_id: 1},{name: :user_id_index})
# Mock a belongs_to relationship with User model
def user
User.find(self.user_id)
end
end
# user.rb
class User < ActiveRecord::Base
# Mock a has_many relationship with VisitSession Mongoid model
def visit_sessions
VisitSession.where(user_id: self.id)
end
end
Of course you won't have all the AR methods on VisitSession Mongoid model but you'll at least be able to mock the relationship between the two fairly well.
Hope this helps.
I don't see any reason why you couldn't have both ActiveRecord and Mongoid models in the same application. That being said, I'm almost certain that you'll run into issues if you try to create relationships between your ActiveRecord and Mongoid models.
If your ActiveRecord models are heavily inter-related, but better suited to a document structure, then I would suggest just biting the bullet and converting them all to Mongoid documents. I had to do this recently on a (large-ish) project, and it's significantly less stressful than you would think.
If you have good unit tests for your models, then it should be a total snap. If you don't - write your unit tests first, make sure they pass with ActiveRecord and then start migrating things over to Mongoid.
i created a module for spoofing the relation in active record models.
module MongoRelations
def belongs_to_mongo(name, options = {})
id_name = "mongo_#{name}_id".to_sym
mongo_model = options[:through] || "Mongo::#{name.to_s.camelize}".constantize
define_method(name) do
id = send(id_name)
mongo_model.find(id) if id.present?
end
define_method("#{name}=") do |value|
send("#{id_name}=".to_sym, value.try(:id).to_s)
end
end
end
In my SQL table, I name my mongo relations using the convention mongo_XXX_id, instead of XXX_id
I also namespace all my mongo models under Mongo::
in my active record model
class Foo < ActiveRecord::Base
belongs_to_mongo :XXX
end
which allows
Foo.new.XXX = Mongo.find('123')
Foo.XXX
or
Foo.new.XXX_id = '123'
Foo.XXX
... just for tracking purpose,
I'd like to add something I just found out on this field:
DRY up your SQL+NoSQL Rails projects

rails model relationship and migration

I have some problem trying to understand when building a rails app with several models and relation ships between them...
If I take a basic example like a model Group, a model User and a model Car
class Group < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
belongs_to :group
has_many :cars
end
class Car < ActiveRecord::Base
belongs_to :user
end
Will those relation ship statements automatically create the following functions:
group.users
user.group
user.cars
car.user
It seems that we sometimes need to have to create "references" in migration (like adding a reference toward User in Car table) but is this always required ?
In this case, what is the difference of creating the migration and of adding the relationship statement in the models ? I sometimes have the feeling this is used for the same purpose.
Thanks a lot for your help,
Regards,
Luc
The association declarations are there for Rails only. You have to define the foreign keys (references) in the database, so that Rails can properly save the data.
Remember, despite all the magic, it's still backed by a relational database, so good practices there will pay off in the long run.

Resources