{"id":16380993,"url":"https://github.com/anthonator/soulless","last_synced_at":"2025-10-06T15:34:04.618Z","repository":{"id":11512931,"uuid":"13993289","full_name":"anthonator/soulless","owner":"anthonator","description":"Rails models without the database (and Rails)","archived":false,"fork":false,"pushed_at":"2018-04-26T14:16:13.000Z","size":108,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-18T17:24:33.372Z","etag":null,"topics":[],"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/anthonator.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-10-30T16:57:44.000Z","updated_at":"2023-03-17T12:03:09.000Z","dependencies_parsed_at":"2022-09-13T17:52:16.443Z","dependency_job_id":null,"html_url":"https://github.com/anthonator/soulless","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonator%2Fsoulless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonator%2Fsoulless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonator%2Fsoulless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonator%2Fsoulless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anthonator","download_url":"https://codeload.github.com/anthonator/soulless/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245052659,"owners_count":20553162,"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","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":[],"created_at":"2024-10-11T03:53:05.166Z","updated_at":"2025-10-06T15:34:04.509Z","avatar_url":"https://github.com/anthonator.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Soulless\n\nRails models without the database (and Rails). Great for implementing the form object pattern.\n\n[![Build Status](https://travis-ci.org/anthonator/soulless.png?branch=master)](https://travis-ci.org/anthonator/soulless) [![Dependency Status](https://gemnasium.com/anthonator/soulless.png)](https://gemnasium.com/anthonator/soulless)\n [![Coverage Status](https://coveralls.io/repos/anthonator/soulless/badge.png?branch=master)](https://coveralls.io/r/anthonator/soulless?branch=master) [![Code Climate](https://codeclimate.com/github/anthonator/soulless.png)](https://codeclimate.com/github/anthonator/soulless)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'soulless'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install soulless\n\n## Usage\n\nJust define a plain-old-ruby-object, inherit from `Soulless::Model` and get crackin'!\n\n```ruby\nclass UserSignupForm \u003c Soulless::Model\n  attribute :name, String\n  attribute :email, String\n  attribute :password, String\n\n  validates :name, presence: true\n\n  validates :email, presence: true,\n                    uniqueness: { model: User }\n\n  validates :password, presence: true,\n                       lenght: { is_at_least: 8 }\nend\n```\n\n### Validations and Errors\n\nSoulless lets you define your validations and manage your errors just like you did in Rails.\n\n```ruby\nclass UserSignupForm \u003c Soulless::Model\n\n  ...\n\n  validates :name, presence: true\n\n  validates :email, presence: true,\n                    uniqueness: { model: User }\n\n  validates :password, presence: true,\n                       lenght: { minimum: 8 }\n\n  ...\n\nend\n```\n\nCheck to see if your object is valid by calling `valid?`.\n\n```ruby\nform = UserSignupForm.new(name: name, email: email)\nform.valid? # =\u003e false\n```\n\nSee what errors are popping up using the `errors` attribute.\n\n```ruby\nform = UserSignupForm.new(name: name, email: email)\nform.valid?\nform.errors[:password] # =\u003e [\"is too short (minimum is 8 characters)\"]\n```\n\n#### Uniqueness Validations\n\nIf you're using Soulless in Rails it's even possible to validate uniqueness.\n\n```ruby\nclass UserSignupForm \u003c Soulless::Model\n\n  ...\n\n  validates :primary_email, presence: true,\n                            uniqueness: { model: User, attribute: :email }\n\n  ...\n\nend\n```\n\nJust let the validator know what ActiveRecord model to use when performing the validation using the `model` option.\n\nIf your Soulless object attribute doesn't match up to the ActiveRecord model attribute just map it using the `attribute` option.\n\n### Callbacks\n\nSoulless supports the validation callback. You can use this callback as you\nwould on a Rails model.\n\n```ruby\nclass Person \u003c Soulless::Model\n\n  attribute :name, String\n\n  validates :name, presence: true\n\n  before_validation :change_name_to_bart_simpson\n\n  private\n  def change_name_to_bart_simpson\n    self.name = 'Bart Simpson'\n  end\nend\n\nperson = Person.new(name: 'Anthony')\nperson.valid?\nperson.name # =\u003e \"Bart Simpson\"\n```\n\n### Dirty Attributes\n\nDirty attribute allow you to track changes to a Soulless object before it's saved.\n\n```ruby\nperson = Person.name(name: \"Anthony\", spouse: { name: \"Mary Jane Watson\" })\nperson.name = 'Peter Parker'\nperson.changed? # =\u003e true\nperson.changed # =\u003e [\"name\"]\nperson.changes # =\u003e { name: [\"Anthony\", \"Peter Parker\"] }\nperson.name_changed? # =\u003e true\nperson.name_was # =\u003e \"Anthony\"\nperson.name_change # =\u003e [\"Anthony\", \"Peter Parker\"]\n```\n\n### Inheritance\n\nOne of the biggest pitfalls of the form object pattern is duplication of code. It's not uncommon for a form object to define attributes and validations that are identical to the model it represets.\n\nTo get rid of this annoying issue Soulless implements the `#inherit_from(klass, options = {})` method. This method will allow a Soulless model to inherit attributes and validations from any Rails model, Soulless model or Virtus object.\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  validates :name, presence: true\n\n  validates :email, presence: true,\n                    uniqueness: { case_insensitive: true }\nend\n```\n\n```ruby\nclass UserSignupForm \u003c Soulless::Model\n  inherit_from(User)\nend\n```\n\nThe `UserSignupForm` has automatically been defined with the `name` and `email` attributes and validations.\n\n```ruby\nUserSignupForm.attributes # =\u003e name and email attributes\nform = UserSignupForm.new\nform.valid? # =\u003e false\nform.errors.messages # =\u003e { name: [\"can't be blank\"], email: [\"can't be blank\"] }\n```\n\nIf your model is using the uniqueness validator it will automatically convert it to the [uniqueness validator provided by Soulless](https://github.com/anthonator/soulless#uniqueness-validations).\n\nThe `#inherit_from(klass, options = {})` method also allows you to provide options for managing inherited attributes.\n\nIf you don't want to inherit the `email` attribute define it using the `exclude` option.\n\n```ruby\nclass UserSignupForm \u003c Soulless::Model\n  inherit_from(User, exclude: :email)\nend\n\nUserSignupForm.attributes # =\u003e email will not be inherited\nform = UserSignupForm.new\nform.valid? # =\u003e false\nform.errors.messages # =\u003e { name: [\"can't be blank\"] }\n```\n\nYou can also flip it around if you only want the `name` attribute by using the `only` option.\n\n```ruby\nclass UserSignupForm \u003c Soulless::Model\n  inherit_from(User, only: :name)\nend\n\nUserSignupForm.attributes # =\u003e email will not be inherited\nform = UserSignupForm.new\nform.valid? # =\u003e false\nform.errors.messages # =\u003e { name: [\"can't be blank\"] }\n```\n\n#### Available Options\n\n`only` - Only inherit the attributes and validations for the provided attributes. Any attributes not specified will be ignored. Accepts strings, symbols and an array of strings or symbols.\n\n`exclude` - Don't inherit the attributes and validations for the provided attributes. Accepts strings, symbols and an array of strings or symbols.\n\n`skip_validators` - Only inherit attributes. Don't inherit any validators. Accepts a boolean.\n\n`use_database_default` - Use the value of the `default` migration option as the default value for an attribute. Accepts either a boolean (for all attributes), a string or symbol for a single attribute or an array of strings and symbols for multiple attributes.\n\n`additional_attributes` - Used to specify attributes that cannot automatically be added to the form model. These are generally attributes that have been specified using `attr_accessor`. Accepts a string, symbol or an array of strings and symbols for multiple attributes.\n\n`validate_attribute_on` - By default any validation that specifies an `:on` option will not be inherited. This option will allow you to inherit a validator that uses the `:on` with a specific value. Example usage: `validate_password_on: :create`. Accepts a string or symbol. This option will accept any value that the Rails `:on` validator option can accept.\n\n### Serialization\n\nSoulless automatically handles serializing and deserializing Soulless models\nusing the standard ActiveRecord serialization methods. Serialization currently\nonly handles JSON and array data types.\n\n```ruby\nclass Profile \u003c Soulless::Model\n  attribute :first_name, String\n  attribute :last_name, String\n\n  ...\nend\n\nclass User \u003c ActiveRecord::Base\n  serialize :profile, Profile\n\n  ...\nend\n\nuser = User.new\nuser.profile = { first_name: 'Anthony', last_name: 'Smith' }\nuser.profile # =\u003e Profile(first_name: 'Anthony', last_name: 'Smith')\n```\n\nIf you want to implement your own serialization/deserialization process just\nimplement your own `self.load(value)` and `self.dump(value)` methods on your\nmodel.\n\n### I18n\n\nDefine locales similar to how you would define them in Rails.\n\n\n```yaml\nen:\n  soulless:\n    errors:\n      models:\n        person:\n          attributes:\n            name:\n              blank: \"there's nothing here\"\n```\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n## Credits\n[![Sticksnleaves](http://sticksnleaves-wordpress.herokuapp.com/wp-content/themes/sticksnleaves/images/snl-logo-116x116.png)](http://www.sticksnleaves.com)\n\nSoulless is maintained and funded by [Sticksnleaves](http://www.sticksnleaves.com)\n\nThanks to all of our [contributors](https://github.com/anthonator/soulless/graphs/contributors)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanthonator%2Fsoulless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanthonator%2Fsoulless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanthonator%2Fsoulless/lists"}