No commit activity in last 3 years
No release in over 3 years
Link objects of different models in a single common table named "any_links" using has_many_to_many, has_many_to_one or has_one_to_many new methods. There is no need for creating a new table for any connection type, nor buiding an "has_many through" associations.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.14
~> 10.0
~> 3.0
~> 1.3

Runtime

 Project Readme

AnyLinks

AnyLinks is an ActiveRecord extension which enables simple linking any model to any other model.

It requires a single table holding the connection of all types, and a single statement for each linked model. No pass-through model required.

The links can be many-to-many or one-to-many, and can link also any model to itself, with bidirectional connection.

It is a simple-to-use extension to the polymorphic schema, but without the complexity and with much more capabilities.

Installation

Add this line to your application's Gemfile:

gem 'active_record-any_links'

And then execute:

$ bundle

Or install it yourself as:

$ gem install active_record-any_links

Getting Started

1. Create any_links table

AnyLinks requires an any_links table to store the links. You can create the table by adding the following migration, and running:

 rake db:migrate
 class CreateAnyLinks < ActiveRecord::Migration
   def up
     create_table :any_links do |t|
       t.integer :id1, null: false
       t.string :type1, null: false
       t.integer :id2, null: false
       t.string :type2, null: false

       t.timestamps
     end

     change_table :any_links do |t|
       t.index [:id1, :type1, :id2, :type2], unique: true
       t.index [:id1, :type1, :type2]
       t.index [:id2, :type2, :type1]
     end
   end

   def down
     drop_table :any_links
   end
 end

2. Linking models

In the following example we link the User model to Group model, using many to many relationship.

class User < ActiveRecord::Base
  has_many_to_many :groups
end

class Group < ActiveRecord::Base
  has_many_to_many :users
end

3. Using the linked models

Use the linked models the same way you use any has_many relationship. examples:

@user.groups = [group, group]
@group.user_ids # => [4,7,9]
@group.users.exists?
@group.users.where(first_name: "James")

AnyLinks statements

There are three statements added to ActiveRecord:

has_many_to_many
has_many_to_one
has_one_to_many

Each one of them creates a bundle of helper methods.

has_many_to_many

has_many_to_many Specifies a many-to-many bi-directional association. Regularly, has_many_to_many is declared in each side of the association models. It will also work if declared only on one side of the association. However, doing so will not generate the dynamic methods on the other side, making the connections seen from one side only

has_many_to_many can be used even on the same model:

class User < ActiveRecord::Base
  has_many_to_many :friends, class_name: User
end

The previous link is also bidirectional! The associations, for all types and collections, are stored together in a single table called 'any_links'. The table contain all links between all instances of any type. Every record represent a link, which contains id and type for each side of the connection, which are called id1, type1, and id2, type2 respectively. Since the links are bi-directional, theoretically, any object can be represented as the left side link (#1) or right side (#2). For simplicity and performance, the links are stored thus the "smallest" type is in the left. Any link betwwen 'Incident' and 'Change' is stored as the Change object in the left, since the string types are 'Change' < 'Incident'. This technique is 'Hidden' and transparent for the has_many_to_many consumer.

For association of the same type - (e.g. Incident has_many incidents): do standard declaration in the Class (i.e. has_many_to_many ). In that case, a callbacks of after create and after destroy shall be called in the relevant any_link class. To overcome association methods that skip callbacks (as clear method) we override the delete_all association method of has many by passing a block with the new function. Notice that calling with 'dependent' param in this case shall raise an error since it doesn't have a meaning for once and secondly this param cannot be passed to destroy_all which is the callback that we call.

A set dynamic methods are generated for altering the set of linked objects. They are the same methods as added by has_many decleration. The following methods for retrieval and query of collections of associated objects will be added:

Auto-generated methods

        others
        others=(other,other,...)
        other_ids
        other_ids=(id,id,...)
        others<<
        others.push
        others.concat
        others.build(attributes={})
        others.create(attributes={})
        others.create!(attributes={})
        others.size
        others.length
        others.count
        others.sum(args*,&block)
        others.empty?
        others.clear
        others.delete(other,other,...)
        others.delete_all
        others.destroy_all
        others.find(*args)
        others.exists?
        others.uniq
        others.reset

Options

  [:class_name]
    Specify the class name of the association. Use it only if that name can't be inferred
    from the association name. So <tt>has_many_to_many :products</tt> will by default be linked
    to the Product class, but if the real class name is SpecialProduct, you'll have to
    specify it with this option.

Examples

      has_many_to_many :authors                             # linked class is Author
      has_many_to_many :authors, class_name: "Person"       # specify that linked class is Person

has_one_to_many, has_many_to_one

has_one_to_many Specifies a one-to-many bi-directional association. The has_one_to_many is declared in one side of the association models. The other side must declare the opposite direction with has_many_to_one decleration. Notice: It differ from has_many_to_many, which will work also if only one side of the association is declared.

The has_one_to_many and has_many_to_one use the has_many_to_many to create the relatioships, in the same table, but with restriction to a single connection from the has_one side. It means that the decleration creates plural helper functions as above in additition to the following singular helper functions.

Singular Auto-generated methods

  other
  other=other
  other_id
  other_id=id

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Hagai-Arzi/active_record-any_links.