Creating two different objects (from different models) at the same time - Rails - ruby-on-rails

I have two models: Product and Category, which I want to tie together.
Say each product has a unique category, how do I set Rails to create (or generate) a new Category object each time a user creates a new Product object?

class Product < ActiveRecord::Base
after_create do
Category.create product: self
end
has_one :category
end
class Category < ActiveRecord::Base
belongs_to :product
end
and in console
> a= Product.create
> a.category
=> #<Category id: 1, product_id: 5, created_at: "2015-11-04 12:53:22", updated_at: "2015-11-04 12:53:22">

First of all, if a category belongs so much to a product, why don't you add it to the product model? But still if you insist on the way you would like to have it coded, you can take advantage of callbacks like after_create, and write code for creating a category over there. This way, whenever a product is created, an associated category will be created along the way.
class Product < ActiveRecord::Base
has_one :category
after_create :create_category
private
def create_category
# code for creating an associated category
end
end
Note: Most of the time, I need to save mobile number for a user in the database, and each user has a unique mobile number - so instead of defining a new model for mobile number, I tend to have it inside users table. But if the information for mobile number expands like its operator name, country code - I'd definitely split it into a separate table.

Related

How rails ActiveRecord or Nosql ORM like mongoid handle has_many, has_one side of the relationship?

Lets Say I have User and a Team model.
class User
#some fields
has_one :team
end
class Team
#some fields
belongs_to :user
end
Now when i do User.last.update(team: Team.last), it stores user_id: foreign key in team document. Prety common stuff.
I want to know what ORMs do/store on the has_one/has_many models(User) just like they store foreign_key on belongs_to(Team) model so that User.last.team query actually gives me a team. Does it create instance methods on User model like
def team
Team.where(user_id: self.id).first
end
Am I wrong?
Okay I can definitely explain what's going on here. Given the following models:
class User
include Mongoid::Document
#some fields
has_one :team
end
class Team
include Mongoid::Document
#some fields
belongs_to :user
end
We can create the documents as follows:
Team.create!(user: User.create!)
This is equivalent to what you did in the OP. Now, this yields the following documents:
> db.users.find()
{ "_id" : ObjectId("62d9ad683282a400a799c7e7") }
> db.teams.find()
{ "_id" : ObjectId("62d9ad683282a400a799c7e8"), "user_id" : ObjectId("62d9ad683282a400a799c7e7") }
As you can see, the foreign key is stored on the belongs_to side of the relationship. Now, when trying to retrieve the team from the user:
User.last
# => #<User _id: 62d9adc63282a400c637b234, >
User.last.team
The take method (as of Mongoid 8, before that, the first method was used) is used on the following criteria:
#<Mongoid::Criteria
selector: {"user_id"=>BSON::ObjectId('62d9adc63282a400c637b234')}
options: {}
class: Team
embedded: false>
To put this into layman's terms, we are searching the Team collection for a document with the same value for user_id as our user's _id.
So yes, your idea is almost correct, it's more like:
Team.where(user_id: user._id).take
The difference between take and first is that take doesn't put a sort on _id, while first does. Using take is a little more efficient.

Query for only nested associated records

I have the follow set up in my models:
class Project < ApplicationRecord
has_many :plans
end
class Plan < ApplicationRecord
belongs_to :project
has_many :documents
end
class Document < ApplicationRecord
belongs_to :plan
end
I'm trying to make a query in which I can easily return all nested associated records. For example, I want to be able to do something like: Project.documents and get all documents for a project. I've tried this with an includes like so:
Project.includes({plans: [:documents]})
but that doesn't appear to give me what I want when I call Project.documents. I really only want all documents for a project, so don't really need to include plans. Is there an easy way todo this in Rails 6?
The relationship between a document and a plan goes first through project, so you can use joins, specifying that a document belongs to a plan, and that a plan belongs to a project. After that you can filter the rows by selecting the plans (whose document belongs to) having the project_id equals ...:
Document.joins(plan: :project).where(plans: { project_id: ... })

Rails: Size is not working on a has_many relationship

I have two models, Student and Absence, with the relationship:
class Student < ActiveRecord::Base
has_many :absences
class Absence < ActiveRecord::Base
belongs_to :student
I want to see how many absences have been logged for each student, and show that total on the student's #show page. In the console, I'm able to query this by calling:
a = Student.find(1)
a.absences.size
However, I can't seem to get it to work in the app itself. Would the preferred way of querying this in the app be in the model or the controller?
This will never work, since you are calling association on the model, not instance. At first you should fetch a student and then count associated records:
Student.find(<id here>).absences.size
To aggregate this information for all students, you can add absence_count attribute accessor in Student model:
attr_accessor :absence_count
and then do something like this:
Student.includes(:absences).each{|s| s.absence_count = s.absences.size}
If you just need to output it in the view, then you can try the following:
Student.includes(:absences).each do |student|
puts student.absences.size
end

Limiting has_one relationships in Rails

I have two ActiveRecord models like so:
class User < ActiveRecord::Base
  has_one :contact_information
end
class ContactInformation < ActiveRecord::Base
  belongs_to :user
end
This is my setup for a One-to-One relationship between the user and contact information table.
The issue I am encountering is when I create a new contact_information entry that points to a user that already has a existing contact_information, the new record is created including the relationship, meaning I have two contact_information records pointing to the same user, even though it is a one-to-one relationship.
Rails seems to pick the first record and returns it.
For example in irb:
> User.create
> ContactInformation.create(user: User.last)
> ContactInformation.create(user: User.last)
> ContactInformation.all
=> #<ActiveRecord::Relation [#<ContactInformation id: 3, created_at: "2016-01-04 22:28:11", updated_at: "2016-01-04 22:28:11", user_id: 2>, #<ContactInformation id: 4, created_at: "2016-01-04 22:28:18", updated_at: "2016-01-04 22:28:18", user_id: 2>]>
Both with user_id set to 2.
The ideal scenario would be a validation failure from contact_information when a record is created for a user that already has a contact_information.
I came up with this initial custom validation method for the Contact Information model that kind of works for creation (Need to make one for update)
validate :check_parent, on: :create
def check_parent
if user.contact_information.id.nil?
errors.add(:user, "already has contact information")
end
end
I am wondering if there is a more rails-y way of solving this problem? Or will I need to create a custom validator like this for all has_one relationships?
Thanks
A few options are available to you.
You could use validates_uniqueness_of on your ContactInformation model:
class ContactInformation < ActiveRecord::Base
validates_uniqueness_of :user_id
end
However, this will query the database to ensure that no duplicates exist on every save and could be very slow.
Another option is to have your database handle this for you. Depending on what you're using ActiveRecord with, adding a uniqueness constraint on the user_id column of your contact_informations table could be valuable.
I would suggest not using validates_uniqueness_of if your dataset is large, otherwise it might be perfect.
Make sure you at least add an index to the user_id column on that table:
class AddIndexOnContactInformationToUserId < ActiveRecord::Migration
add_index :contact_information, :user_id
end
You will need to have a validation but you don't need a custom one:
class ContactInformation < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :user_id
end
You may also want to add a unique index in the database for that column. It would not only make the query performant but will also protect your app against race conditions in case you are running multiple instances of it in parallel.

has_and_belongs_to_many why would I want to have attr_accessible of the other model?

If I have two models: Experience and Category which are a many to many association, that looks like this:
class Experience < ActiveRecord::Base
has_and_belongs_to_many :categories
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :experiences
end
Why sometimes people add a:
attr_accessible :category_ids in the Experience model? I have found myself having to do that so in Rails admin gem I can add categories to a particular experience, but I fail to see why.
If you setup a HABTM relation between Experience and Category, you have, besides other things, available method Experience#category_ids. This method returns individual ids of categories the current experience has.
Experience.first.category_ids
Now this method has also Experience#category_ids= variant, so you can use it to assign ids of categories:
Experience.first.category_ids = [1, 2, 3]
Now when you have a form that let's you select categories for an experience, you have only the ids of the selected categories. When you submit the form, those ids get passed to Experience#category_ids= via mass assigment and if you don't have this category_ids in attr_accessible, you get an error.

Resources