Many-to-many on the same table in Phoenix - many-to-many

Using Ecto v2.2.6, Phoenix 1.3
I have a table of people (who happen to be related to each other), and another table of relations for those people. The relations table lists the id of the parent in one column, and the id of the child in another.
mix phx.gen.json Accounts Person persons name:string age:integer
mix phx.gen.json Accounts Relationship relationships parent_id:references:persons child_id:references:persons
Now, I am about to add belongs_to relationships in the relationships schema (which currently looks like this)
schema "relationships" do
field :parent_id, :id
field :child_id, :id
timestamps()
end
But without the ability to set the id explicitly, it would looks like this (which doesn't seem to work)
schema "relationships" do
belongs_to :person UserRelations.Accounts.Person
belongs_to :person UserRelations.Accounts.Person
timestamps()
end
How can I write the relationships schema so that I capture these belongs_to relationships?
Edit
I have set the schema up like this, as per suggestion:
schema "relationships" do
belongs_to :parent UserRelations.Accounts.Person
belongs_to :child UserRelations.Accounts.Person
timestamps()
end
I have tried to do something similar with the Person schema as well:
schema "persons" do
field :age, :integer
field :name, :string
many_to_many :parent_of, UserRelations.Accounts.Person, join_through: "relationships"
many_to_many :child_of, UserRelations.Accounts.Person, join_through: "relationships"
timestamps()
end
However, when I try to access these relationships (I am doing this through an absinthe/graphql schema), I see that it is looking for a person_id somewhere:
[error] Task #PID<0.400.0> started from #PID<0.395.0> terminating
** (Postgrex.Error) ERROR 42703 (undefined_column): column f2.person_id does not exist

The name of the two belongs_to relationships need to be different. For example:
belongs_to :child, UserRelations.Accounts.Person
belongs_to :parent, UserRelations.Accounts.Person
With these names, Ecto will infer the foreign key correctly: child_id and parent_id.
For the many_to_many, you need to provide join_keys to both as Ecto can't infer it from the name of the relationship and the schema module.
For the first one, you need to add:
, join_keys: [parent_id: :id, child_id: :id]
And for the second one:
, join_keys: [child_id: :id, parent_id: :id]

Related

Optional relation between two model objects in rails

I have two objects A and B in my Rails model which have an optional one-to-one relationship. Say, A can have 0 or 1 B. In most cases A has 0 Bs.
Also, other such relationships can be introduced between A and other objects X, Y, Z later.
Given that these are one-to-one relations, a has-a relationship between A and B makes sense. But given that it is an optional and infrequent relationship, and also that other such relationships can be introduced later with A, adding a new column for each new relationship seems questionable.
How can I model this relationship better? Should using a correlation table between A and B make better sense in this particular case?
You would add the id of Model A to all the other models, and Model A would has_one for each of them.
Each other would belong_to Model A
I would go with the presented solution, that is adding a new column b_id to A.
Else you could create a third model C which holds both a_id and b_id and then have A access B through C.
# Pluralization is off here. Remember to pluralize has_many
class A < ActiveRecord::Base
has_many :c
has_many :b, through: :c
end
class B < ActiveRecord::Base
has_many :c
has_many :a, through: :c
end
class C < ActiveRecord::Base
belongs_to :a
belongs_to :b
end
This would prevent any null values in the database. This is the solution I use most often when I will build something around the relation, since now you have a place to store more information about the relation.
You could also make C polymorphic so that it can store any model (not only A). http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Add a reference column using migration having non standard primary key column

I have a model which has a non-rails conventional primary key.
class Guoid < ActiveRecord::Base
self.primary_key = :guoid
end
and related migration
class CreateGuoids < ActiveRecord::Migration
def change
create_table :guoids, id: false do |t|
t.integer :guoid, limit: 8, auto_increment: true, primary_key: true
t.integer :parent_guoid, limit: 8
t.string :resource_type, limit: 60
end
end
end
Now I want to reference this model in another model and trying to create migration using references which doesn't to work.
class ContentUnit < ActiveRecord::Base
self.primary_key = :guoid
end
class Content < ActiveRecord::Base
self.primary_key = :guoid
belongs_to :user
belongs_to :content_unit
end
and related migration
class CreateContents < ActiveRecord::Migration
def change
create_table :contents, id: false do |t|
t.references :content_unit, index: true, foreign_key: true
t.references :user, index: true, foreign_key: true
end
end
end
When I run the migration, I am getting following error.
Mysql2::Error: Can't create table `myapp_development_cr1`.`#sql-54a_308` (errno: 150 "Foreign key constraint is incorrectly formed"): ALTER TABLE `contents` ADD CONSTRAINT `fk_rails_823443bd0d`
FOREIGN KEY (`content_unit_id`)
REFERENCES `content_units` (`id`)
I am expecting to create content_unit_guoid foreign key in contents table referencing guoid in guoids table.
I used activerecord-mysql-awesome gem to work well with non-rails convention primary keys.
Here is a trigger which first creates a record in guids table and use it's pk as pk of the target table.
DELIMITER $$
CREATE TRIGGER `content_before_insert` BEFORE INSERT ON `content`
FOR EACH ROW BEGIN
IF NEW.guoid = 0 THEN
INSERT INTO `content`.guoids (resource_type)
VALUES('Content');
SET NEW.guoid = LAST_INSERT_ID();
END IF;
END
$$
DELIMITER ;
That is not a viable database design for ActiveRecord or even in general.
ActiveRecord (and any decent ORM) requires each table to have a primary key. This is what enables relations and lets Rails differentiate records.
class Content < ActiveRecord::Base
self.primary_key = :guoid
belongs_to :user
end
This will never work since self.primary_key = :guoid references contents.guoid not guoids.guoid. You cannot use relations in ActiveRecord as primary keys. Even if you could this would really be a performance killer since every query would need to join the guoids table - even recursively!
Rails is strongly convention driven and really smiles on you if you take a little time to learn the Rails way vs fighting the framework to make it work like framework X. The rails guides are a good place to start.
Stick with id for primary keys and _id for foreign key columns. You will have less fuss and not be treated like idiots if you have to collaborate with other developers.
There are valid cases where you would want to use unique identifiers (UUIDs) instead of auto-incrementing values. For example if you have several databases auto-incrementing values can cause race conditions. But in that case you still need to use a primary key on each table - the difference is simply the contents of the primary key.
This is either done by generating a hashes with a algorithm that has a low chance of collision on the application level or more recently by using binary UUID types in the database. The later being preferable today.
Not by using relations. AR just does not work that way.
http://geekhmer.github.io/blog/2014/12/06/using-uuid-as-primary-key-in-ruby-on-rails-with-mysql-guide/
http://blog.arkency.com/2014/10/how-to-start-using-uuid-in-activerecord-with-postgresql/
Using non-standard foreign keys.
The belongs_to and reference macros are an example of things that just work if you follow the conventions.
For foreign key constrainst that does not match the conventions you need to manually create it in the migration:
class CreateStores < ActiveRecord::Migration
def change
create_table :contents do |t|
t.references :manger, index: true, foreign_key: false
end
add_foreign_key :stores, :users, column: 'manager_id', primary_key: 'uuid'
end
end
Note that this will not fix your issue since your general approach is not viable!
So, your trying to make a foreign key from the contents table to the guoids table using this correct?
t.references :content_unit, index: true, foreign_key: true
references takes a table name as the argument and tries to find a column called id on it to make the foreign key between the tables. So you can see in your error message its trying to find the column id on content_units table. This is in no way referencing your guoids.
Its perfectly reasonable to want globally unique identifiers (typically GUID's or UUID's, however i'm not sure why you are storing them on a separate table and then (i'm assuming) going to make everything foreign key to it creating some massive many to many table that connects every table in your database? Seems really unscalable. Postgress handles uuid's for you nicely, but as I have in the past, it looks like your using mysql. Here is how I have done it.
Models
Class Teacher < ActiveRecord::Base
has_many :students
before_validation :generate_id
self.primary_key = :id
private
def generate_id
write_attribute(:id, SecureRandom.uuid) unless read_attribute(:id)
end
end
Class Student < ActiveRecord::Base
belongs_to :teacher
end
Migrations
create_table :teachers, id: false do |t|
t.string :id, null: false, unique: true, limit: 36
end
create_table :students do |t|
t.string :teacher_id, limit: 36
end
add_foreign_key :model_a, :model_b

How to add link a table to a row

I'm developing a Ruby on Rails application with MySQL and i have a Categories table in my Database.
I want to add sub categories to one of the categories.
i thought of creating a new table for sub categories or just add them to the same Categories table. can anyone help?
As sub_category is nothing but a category with a parent association, you could just store both categories and sub_categories in one table and wire up the association. This type of association is referred as self join association.
Try the following:
class Category < ActiveRecord::Base
has_many :sub_categories, class_name: 'Category', foreign_key: :parent_id
belongs_to :parent, class_name: 'Category'
end
Refer to Self Joins Association for more info.
You need to setup the following migration:
class AddParentIdToCategories < ActiveRecord::Migration
def change
add_column(:categories, :parent_id, :integer)
end
end
And run the migration:
bundle exec rake db:migrate
If you are expecting your tree structure to be more complex, for example, sub_categories having sub_sub_categories and more, then you could take a look at several gems that abstract this feature.
Refer to acts_as_tree, and ancestry fore more info,

Modeling my Database

this is my first post on here. My question is pertaining to model relationships.
I'm new to rails and a bit confused as to what type of associations I need. I checked rails associations but this confused me more. I've tried quite a few things and can't figure out exactly what I need.
I have three models/tables:
Students
Teachers
Groups (as in classes)
I have it setup as:
teacher -> belongs_to :group
student -> belongs_to :group
group -> has_many :students & has_many :teachers
This setup works okay because it allows me to make calls like:
g.students.all
s.group.name
However I would like to be able to make a view page that generates all of the Groups(classes) that a teacher has. Such as: t.groups.all or t.groups.count
One thing that makes my table unique is that a group(class) can have different teachers. I don't know if I need a has_and_belongs_to_many relationship or a has_many :through relationship.
What is the ideal setup for my 3 tables, what table id's and associations are required?

How can I delete an entry from a HABTM join table in rails?

Through many iterations of testing, I just noticed that my join table that represents a HABTM relationship between two models isn't removing entries when instances of these models get deleted. Do I need to do something special when removing an instance of a model that has HABTM relationships?
Upon closer inspection HABTM relationships should be removing join table entries. However neither HABTM relationships or the relationship I described in the original version (see post history) of this solution will remove those join table entries when you eliminate the record with the delete method. ActiveRecord::Base#delete does not trigger any callbacks, such as the ones a HABTM relationship establishes to remove orphaned entries from the join table. Instead you should be using ActiveRecord::Base#destroy.
You will have to use raw SQL to remove the unneeded entries. If you decide to create a join model, you can iterate through entries in the join model, deleting those without an association.
Example:
class Foo < ActiveRecord::Base
has_many :foo_bars, :dependent => :destroy
has_many :bars, :through => :foo_bars
end
class FooBar < ActiveRecord::Base
belongs_to :foo
belongs_to :bar
end
class Bar < ActiveRecord::Base
has_many :foo_bars, :dependent => :destroy
has_many :foos, :through => :foo_bars
end
FooBar.all.each{|fb| fb.destroy? if fb.foo.nil? || fb.bar.nil? }
The entries in the join table should be getting deleted without you doing anything special. You can add the :delete_sql option to your code to change the behavior if you have a weird situation. Deleting the object on the other side of the join is not a default behavior.