{"id":31661900,"url":"https://github.com/dchacke/entangled","last_synced_at":"2025-10-07T19:03:09.027Z","repository":{"id":27167624,"uuid":"30637178","full_name":"dchacke/entangled","owner":"dchacke","description":"Rails in real time","archived":false,"fork":false,"pushed_at":"2017-03-25T21:17:06.000Z","size":17615,"stargazers_count":108,"open_issues_count":4,"forks_count":15,"subscribers_count":17,"default_branch":"development","last_synced_at":"2025-09-25T14:00:39.406Z","etag":null,"topics":["javascript","puma","rails","realtime","redis","ruby","websockets"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dchacke.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-02-11T08:03:53.000Z","updated_at":"2024-04-11T17:14:28.000Z","dependencies_parsed_at":"2022-08-17T17:40:17.360Z","dependency_job_id":null,"html_url":"https://github.com/dchacke/entangled","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dchacke/entangled","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dchacke%2Fentangled","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dchacke%2Fentangled/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dchacke%2Fentangled/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dchacke%2Fentangled/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dchacke","download_url":"https://codeload.github.com/dchacke/entangled/tar.gz/refs/heads/development","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dchacke%2Fentangled/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278811847,"owners_count":26050181,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","puma","rails","realtime","redis","ruby","websockets"],"created_at":"2025-10-07T19:02:03.041Z","updated_at":"2025-10-07T19:03:09.020Z","avatar_url":"https://github.com/dchacke.png","language":"Ruby","readme":"# Entangled\n\n[![Codeship Status for dchacke/entangled](https://codeship.com/projects/9fe9a790-9df7-0132-5fb8-6e77ea26735b/status?branch=master)](https://codeship.com/projects/64679)\n\n**This project is not actively maintained at the moment.**\n\nReal time is important. Users have come to expect real time behavior from every website, because they want to see the latest data without having to reload the page. Real time increases their engagement, provides better context for the data they're seeing, and makes collaboration easier.\n\nEntangled stores and syncs data from ActiveRecord instantly across every device. It is a layer behind your models and controllers that pushes updates to all connected clients in real time. It is cross-browser compatible and offers real time validations.\n\nCurrently, Entangled runs on Rails \u003e= 4.0 and Ruby \u003e= 2.0.0. In the front end, libraries are available in [plain JavaScript](https://github.com/dchacke/entangled-js) and for [Angular](https://github.com/dchacke/entangled-angular).\n\n## Installation\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'entangled'\n```\n\nNote that Redis and Puma are required as well. Redis is needed to build the channels clients subscribe to, Puma is needed to handle websockets concurrently.\n\nYou need to [install Redis](http://redis.io/download) if you haven't yet. Entangled comes with a [Ruby client for Redis](https://rubygems.org/gems/redis) that will connect to your Redis instance once it's installed.\n\nYou also need to add Puma to your Gemfile:\n\n```ruby\ngem 'puma'\n```\n\nAnd then execute:\n\n    $ bundle\n\n## Usage\nEntangled is needed in three parts of your app: Routes, models, and controllers. Given the example of a `MessagesController` and a `Message` model for a chat app, you will need:\n\n### Routes\nAdd the following to your routes file:\n\n```ruby\nsockets_for :messages\n```\n\nUnder the hood, this creates the following routes:\n\n```shell\n         Prefix Verb URI Pattern                      Controller#Action\n       messages GET  /messages(.:format)              messages#index\n        message GET  /messages/:id(.:format)          messages#show\ncreate_messages GET  /messages/create(.:format)       messages#create\n update_message GET  /messages/:id/update(.:format)   messages#update\ndestroy_message GET  /messages/:id/destroy(.:format)  messages#destroy\n```\n\nNote that websockets don't speak HTTP, so only GET requests are available. That's why these routes deviate slightly from restful routes. Also note that there are no `edit` and `new` actions, since an Entangled controller is only concerned with rendering data, not views.\n\nYou can use `sockets_for` just like `resources`, including the following features:\n\n```ruby\n# Inclusion/exclusion\nsockets_for :messages, only: :index\nsockets_for :messages, only: [:index, :show]\n\nsockets_for :messages, except: :index\nsockets_for :messages, except: [:index, :show]\n\n# Nesting\nsockets_for :parents do\n  sockets_for :children\nend\n\n# Multiple routes at once\nsockets_for :foos, :bars\n\n# ...etc\n```\n\n### Models\nAdd the following to the top of your model (e.g., a `Message` model):\n\n```ruby\nclass Message \u003c ActiveRecord::Base\n  include Entangled::Model\n  entangle\nend\n```\n\nThis will create the callbacks needed to push changes to data to all clients who are subscribed. This is essentially where the data binding is set up.\n\nBy default, the following callbacks will be added:\n\n- `after_create`\n- `after_update`\n- `after_destroy`\n\nYou can limit this behavior by specifying `:only` or `:except` options. For example, if you don't want to propagate the destruction or update of an object to all connected clients, you can do the following:\n\n```ruby\nentangle only: :create\nentangle only: [:create, :update]\n```\n\nCalling `entangle` creates the following channels (sticking with the example of a `Message` model):\n\n```ruby\n# For the collection\n\"/messages\"\n\n# For a member, e.g. /messages/1\n\"/messages/:id\"\n```\n\n`:id` being the record's id, just as with routes.\n\n### Controllers\nYour controllers will be a little more lightweight than in a standard restful Rails app. A restful-style controller is expected and should look like this:\n\n```ruby\nclass MessagesController \u003c ApplicationController\n  include Entangled::Controller\n\n  def index\n    broadcast do\n      @messages = Message.all\n    end\n  end\n\n  def show\n    broadcast do\n      @message = Message.find(params[:id])\n    end\n  end\n\n  def create\n    broadcast do\n      @message = Message.create(message_params)\n    end\n  end\n\n  def update\n    broadcast do\n      @message = Message.find(params[:id])\n      @message.update(message_params)\n    end\n  end\n\n  def destroy\n    broadcast do\n      @message = Message.find(params[:id]).destroy\n    end\n  end\n\n  # def custom_action\n  #   broadcast do\n  #     # do whatever\n  #   end\n  # end\n\nprivate\n  def message_params\n    # params logic here\n  end\nend\n```\n\nNote the following:\n\n- All methods are wrapped in a new `broadcast` block needed to receive and send data to connected clients\n- The `index` action will expect an instance variable with the same name as your controller in the plural form (e.g. `@messages` in a `MessagesController`)\n- The `show`, `create`, `update`, and `destroy` actions will expect an instance variable with the singular name of your controller (e.g. `@message` in a `MessagesController`)\n- The instance variables are sent to clients as stringified JSON\n- Strong parameters are expected\n- The path to your controllers' index action has to match the model's channel for the collection, and the path to your controller's show action has to match the model's channel for a single member (which it will automatically if you stay RESTful)\n- You can add custom actions on top of the five RESTful ones; JSON sent through sockets is available in the params hash\n\n### Server\n\nRemember to run Redis whenever you run your server:\n\n```shell\n$ redis-server\n```\n\nRedis is needed to subscribe and publish to the channels that are created by Entangled internally to communicate over websockets.\n\nIf you store your Redis instance in `$redis` or `REDIS` (e.g. in an initializer), Entangled will use that assigned instance so that you can configure Redis just like you're used to. Otherwise, Entangled will instantiate Redis itself and use its default settings.\n\n### Associations\nWhat if you want to only fetch and subscribe to children that belong to a specific parent? Or maybe you want to create a child in your front end and assign it to a specific parent?\n\nImagine the following Parent \u003e Children relationship in your models:\n\n```ruby\nclass Parent \u003c ActiveRecord::Base\n  include Entangled::Model\n  entangle\n\n  has_many :children\nend\n\nclass Child \u003c ActiveRecord::Base\n  include Entangled::Model\n  entangle\n\n  belongs_to :parent\nend\n```\n\nEntangled takes note of every `belongs_to` association and creates two additional channels for each `belongs_to` association in the child model:\n\n```ruby\n\"/parents/:parent_id/children\"\n\"/parents/:parent_id/children/:id\"\n```\n\nSo in total, the `Child` model will have all of the following channels:\n\n```ruby\n\"/children\"\n\"/children/:id\"\n\"/parents/:parent_id/children\"\n\"/parents/:parent_id/children/:id\"\n```\n\nChannels are deeply nested for child/parent/grandparent etc associations. There is no limit. To get a list of all available channels on a record, you can call the method `channels` on any entangled instance.\n\nTo reflect associations in your front end, you just need to add three things to your app:\n\n- Nest your routes so that they resemble the parent/child relationship:\n\n```ruby\nsockets_for :parents do\n  sockets_for :children\nend\n```\n\n- Adjust the `index` and `create` actions in your `ChildrenController` so that they look like this:\n\n```ruby\nclass ChildrenController \u003c ApplicationController\n  include Entangled::Controller\n\n  # Fetch children of specific parent\n  def index\n    broadcast do\n      @children = Child.where(parent_id: params[:parent_id])\n    end\n  end\n\n  # Create child of specific parent\n  def create\n    broadcast do\n      @child = Child.new(child_params)\n      @child.parent_id = params[:parent_id]\n      @child.save\n    end\n  end\n\n  # show, update and destroy don't need to be nested\nend\n```\n\nCheck out the JavaScript guides to implement associations on the client.\n\n## The Client\nPick if you want to use Entangled with plain JavaScript or with Angular:\n\n- [entangled-js](https://github.com/dchacke/entangled-js)\n- [entangled-angular](https://github.com/dchacke/entangled-angular)\n\nThe following versions are compatible:\n\n| entangled.gem | entangled-js.js | entangled-angular.js |\n|---------------|-----------------|----------------------|\n| 1.5.0         | 1.4.0           | 1.4.0                |\n| 1.4.1         | 1.3.1           | 1.3.1                |\n| 1.4.1         | 1.3.0           | 1.3.0                |\n\n## A Note On Cases\nThe case conventions differ in Ruby and JavaScript. `snake_case` is the standard in Ruby, whereas `camelCase` is the standard in JavaScript.\n\nTo comply with both standards, Entangled automatically converts attribute names to camel case before sending them from the server to the client to comply with JS conventions, and back to snake case before sending them from the client back to the server to comply with Ruby conventions.\n\nAll this means for you is that this enables you to use the conventional case for both environments. For example, a `sender_name` attribute on your model in Rails will turn into a `senderName` attribute in the browser, and vice versa. It would be weird to write camel case in Ruby.\n\n## Planning Your Infrastructure\nThis gem is best used for Rails apps that serve as APIs only and are not concerned with rendering views, since Entangled controllers cannot render views. A front end separate from your Rails app is recommended, either in your Rails app's public directory, or a separate front end app altogether.\n\n## Limitations\nThe gem relies heavily on convention over configuration and currently only works with restful style controllers as shown above. More features will be available soon. See the list of development priorities below.\n\n## Debugging Websockets\nTo debug websockets from your terminal, you can use curl. For example, to do a handshake with a socket at `/messages` (a route you need to have set up), you can do the following:\n\n```shell\ncurl -i -N -H \"Connection: Upgrade\" -H \"Upgrade: websocket\" -H \"Host: echo.websocket.org\" -H \"Origin: http://localhost:3000\" http://localhost:3000/messages\n```\n\nMore information [here](http://www.thenerdary.net/post/24889968081/debugging-websockets-with-curl).\n\n## Development Priorities\nThe following features are to be implemented next:\n\n- Support multiple `hasMany` and `belongsTo` associations in front end\n- Make prefix of create path `create_message` instead of `create_messages`\n- Support `has_one` associations in back end and front end and route helper for single resource\n- Support scoping in back end (see https://github.com/dchacke/entangled-angular/issues/4)\n- Add support for scopes and where clauses in front end once back end can do scopes à la [Spyke](https://github.com/balvig/spyke)\n- Add ability to configure options, such as to specify which Redis instance to use, in initializer through a config block; see need and example [here](https://github.com/dchacke/entangled/pull/229)\n- Display results of interactions to client immediately without going through the server; add server interactions to queue and constantly dequeue; if result from server conflicts with client state, update client accordingly\n- Add offline capabilities, i.e. only dequeue server interactions once internet connection established\n- Add authentication - with JWT?\n- On Heroku, tasks are always in different order depending on which ones are checked off and not\n- Add `$onChange` method to objects\n- Test controllers (see https://github.com/ngauthier/tubesock/issues/41)\n- Add `.destroyAll()` method\n- Support custom actions in front end (see https://github.com/dchacke/entangled-angular/issues/1)\n- Use only one socket for everything\n\n## Contributing\n1. [Fork it](https://github.com/dchacke/entangled/fork) - you will notice that the repo comes with a back end and a front end part to test both parts of the gem\n2. Run `$ bundle install` in the root of the repo\n3. Run `$ bower install` and `$ npm install` in spec/dummy/public\n4. The back end example app resides in spec/dummy. You can run `rails` and `rake` commands in there if you prefix them with `bin/`, e.g. `$ bin/rails s` or `$ bin/rake db:schema:load`. Run your Rails tests in the root of the repo by running `$ rspec`\n5. The front end example app resides in spec/dummy/public. To look at it in your browser, cd into spec/dummy/public and run `$ bin/rails s`. Tests for this part of the app can be located under spec/dummy/public/test and are written with Jasmine. To run the tests, first run `$ bin/rails -e test` to start up the server in test mode, and then run `$ grunt test` in a new terminal tab. It's important to remember that changes you make to the server will not take effect until you restart the server since you're running it in the test environment. Also remember to prepare the test database by running `$ bin/rake db:test:prepare`\n6. The Entangled Angular service resides in spec/dummy/public/app/entangled/entangled.js. This is where you can make changes to the service. A copy of it, living in /entangled.js at the root of the repo, should be kept in sync for it to be available with Bower. Once you're done editing spec/dummy/public/app/entangled/entangled.js, copy it over to /entangled.js\n7. Write your tests. Test coverage is required\n8. Write your feature to make the tests pass\n9. Stage and commit your changes\n10. Push to a new feature branch in your repo\n11. Send me a pull request!\n\n## Credits\nThanks to [Ilias Tsangaris](https://github.com/iliastsangaris) for inspiring the name \"Entanglement\" based on [Quantum Entanglement](http://en.wikipedia.org/wiki/Quantum_entanglement) where pairs or groups of particles always react to changes as a whole, i.e. changes to one particle will result in immediate change of all particles in the group.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdchacke%2Fentangled","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdchacke%2Fentangled","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdchacke%2Fentangled/lists"}