{"id":13747622,"url":"https://github.com/jnunemaker/toystore","last_synced_at":"2025-05-09T09:30:34.094Z","repository":{"id":1864209,"uuid":"2789349","full_name":"jnunemaker/toystore","owner":"jnunemaker","description":"Object mapper for anything that can read, write and delete data","archived":false,"fork":true,"pushed_at":"2013-04-27T14:22:35.000Z","size":2029,"stargazers_count":126,"open_issues_count":1,"forks_count":12,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-18T14:34:33.361Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://jnunemaker.github.com/toystore/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"zerohistory/toystore","license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jnunemaker.png","metadata":{"files":{"readme":"README.md","changelog":"Changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2011-11-16T16:54:19.000Z","updated_at":"2023-12-07T23:12:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jnunemaker/toystore","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jnunemaker%2Ftoystore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jnunemaker%2Ftoystore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jnunemaker%2Ftoystore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jnunemaker%2Ftoystore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jnunemaker","download_url":"https://codeload.github.com/jnunemaker/toystore/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253226262,"owners_count":21874303,"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-08-03T06:01:35.488Z","updated_at":"2025-05-09T09:30:33.815Z","avatar_url":"https://github.com/jnunemaker.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Toystore\n\nAn object mapper for any [adapter](https://github.com/jnunemaker/adapter) that can read, write, delete, and clear data.\n\n## Examples\n\nThe project comes with two main includes that you can use -- Toy::Object and Toy::Store.\n\n**Toy::Object** comes with all the goods you need for plain old ruby objects -- attributes, dirty attribute tracking, equality, inheritance, serialization, cloning, logging and pretty inspecting.\n\n**Toy::Store** includes Toy::Object and adds identity, persistence and querying through adapters, mass assignment, callbacks, validations and a few simple associations (lists and references).\n\n### Toy::Object\n\nFirst, join me in a whirlwind tour of Toy::Object.\n\n```ruby\nclass Person\n  include Toy::Object\n\n  attribute :name, String\n  attribute :age,  Integer\nend\n\n# Pretty class inspecting\npp Person\n\njohn  = Person.new(:name =\u003e 'John',  :age =\u003e 30)\nsteve = Person.new(:name =\u003e 'Steve', :age =\u003e 31)\n\n# Pretty inspecting\npp john\n\n# Attribute dirty tracking\njohn.name = 'NEW NAME!'\npp john.changes       # {\"name\"=\u003e[\"John\", \"NEW NAME!\"], \"age\"=\u003e[nil, 30]}\npp john.name_changed? # true\n\n# Equality goodies\npp john.eql?(john)  # true\npp john.eql?(steve) # false\npp john == john     # true\npp john == steve    # false\n\n# Cloning\npp john.clone\n\n# Inheritance\nclass AwesomePerson \u003c Person\nend\n\npp Person.attributes.keys.sort          # [\"age\", \"name\"]\npp AwesomePerson.attributes.keys.sort   # [\"age\", \"name\", \"type\"]\n\n# Serialization\nputs john.to_json\nputs john.to_xml\n```\n\nOk, that was definitely awesome. Please continue on your personal journey to a blown mind (very similar to a beautiful mind).\n\n### Toy::Store\n\nToy::Store is a unique bird that builds on top of Toy::Object. Below is a quick sample of what it can do.\n\n```ruby\nclass Person\n  include Toy::Store\n\n  attribute :name, String\n  attribute :age,  Integer, :default =\u003e 0\nend\n\n# Persistence\njohn = Person.create(:name =\u003e 'John', :age =\u003e 30)\npp john\npp john.persisted?\n\n# Mass Assignment Security\nPerson.attribute :role, String, :default =\u003e 'guest'\nPerson.attr_accessible :name, :age\n\nperson = Person.new(:name =\u003e 'Hacker', :age =\u003e 13, :role =\u003e 'admin')\npp person.role # \"guest\"\n\n# Querying\npp Person.read(john.id)\npp Person.read_multiple([john.id])\npp Person.read('NOT HERE') # nil\n\nbegin\n  Person.read!('NOT HERE')\nrescue Toy::NotFound\n  puts \"Could not find person with id of 'NOT HERE'\"\nend\n\n# Reloading\npp john.reload\n\n# Callbacks\nclass Person\n  before_create :add_fifty_to_age\n\n  def add_fifty_to_age\n    self.age += 50\n  end\nend\n\npp Person.create(:age =\u003e 10).age # 60\n\n# Validations\nclass Person\n  validates_presence_of :name\nend\n\nperson = Person.new\npp person.valid?        # false\npp person.errors[:name] # [\"can't be blank\"]\n\n# Lists (array key stored as attribute)\nclass Skill\n  include Toy::Store\n\n  attribute :name, String\n  attribute :truth, Boolean\nend\n\nclass Person\n  list :skills, Skill\nend\n\njohn.skills = [Skill.create(:name =\u003e 'Programming', :truth =\u003e true)]\njohn.skills \u003c\u003c Skill.create(:name =\u003e 'Mechanic', :truth =\u003e false)\n\npp john.skills.map(\u0026:id) == john.skill_ids # true\n\n# References (think foreign keyish)\nclass Person\n  reference :mom, Person\nend\n\nmom = Person.create(:name =\u003e 'Mum')\njohn.mom = mom\njohn.save\npp john.reload.mom_id == mom.id # true\n\n# Identity Map\nToy::IdentityMap.use do\n  frank = Person.create(:name =\u003e 'Frank')\n\n  pp Person.read(frank.id).equal?(frank)                # true\n  pp Person.read(frank.id).object_id == frank.object_id # true\nend\n\n# Or you can turn it on globally\nToy::IdentityMap.enabled = true\nfrank = Person.create(:name =\u003e 'Frank')\n\npp Person.read(frank.id).equal?(frank)                # true\npp Person.read(frank.id).object_id == frank.object_id # true\n\n# All persistence runs through an adapter.\n# All of the above examples used the default in-memory adapter.\n# Looks something like this:\nPerson.adapter :memory, {}\n\nputs \"Adapter: #{Person.adapter.inspect}\"\n\n# You can make a new adapter to your awesome new/old data store\nAdapter.define(:append_only_array) do\n  def read(key)\n    if (record = client.reverse.detect { |row| row[0] == key })\n      record\n    end\n  end\n\n  def write(key, value)\n    client \u003c\u003c [key, value]\n    value\n  end\n\n  def delete(key)\n    client.delete_if { |row| row[0] == key }\n  end\n\n  def clear\n    client.clear\n  end\nend\n\nclient = []\nPerson.adapter :append_only_array, client\n\npp \"Client: #{Person.adapter.client.equal?(client)}\"\n\nperson = Person.create(:name =\u003e 'Phil', :age =\u003e 55)\nperson.age = 56\nperson.save\n\npp client\n\npp Person.read(person.id) # Phil with age 56\n```\n\nIf that doesn't excite you, nothing will. At this point, you are probably wishing for more.\n\nLuckily, there is an entire directory full of [examples](https://github.com/jnunemaker/toystore/tree/master/examples) and I created a few power user guides, which I will kindly link next.\n\n## Instrumentation\n\nToyStore comes with a log subscriber and automatic metriks instrumentation. By\ndefault these work with ActiveSupport::Notifications, but only require the\npieces of ActiveSupport that are needed and only do so if you actually attempt\nto require the instrumentation files listed below.\n\nTo use the log subscriber:\n\n```ruby\n# Gemfile\ngem 'activesupport'\n\n# config/initializers/toystore.rb (or wherever you want it)\nrequire 'toy/instrumentation/log_subscriber'\n```\n\nTo use the metriks instrumentation:\n\n```ruby\n# Gemfile\ngem 'activesupport'\ngem 'metriks'\n\n# config/initializers/toystore.rb (or wherever you want it)\nrequire 'toy/instrumentation/metriks'\n```\n\n## ToyStore Power User Guides\n\n* [Wiki Home](https://github.com/jnunemaker/toystore/wiki)\n* [Identity](https://github.com/jnunemaker/toystore/wiki/Identity)\n* [Types](https://github.com/jnunemaker/toystore/wiki/Types)\n* [Exceptions](https://github.com/jnunemaker/toystore/wiki/Exceptions)\n\n## Changelog\n\nAs of 0.8.3, I started keeping a [changelog](https://github.com/jnunemaker/toystore/blob/master/Changelog.md). All significant updates will be summarized there.\n\n## Compatibility\n\n* Rails 3.0.*, 3.1.*, 3.2.*, Sinatra, etc. No Rails 2 (because it uses Active Model).\n* Ruby 1.9.3 only\n\n## Mailing List\n\nhttps://groups.google.com/forum/#!forum/toystoreadapter\n\n## Contributing\n\n* Fork the project.\n* Make your feature addition or bug fix in a topic branch.\n* Add tests for it. This is important so we don't break it in a future version unintentionally.\n* Commit, do not mess with rakefile, version, or changelog. (if you want to have your own version, that is fine, but bump version in a commit by itself so we can ignore when we pull)\n* Send a pull request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjnunemaker%2Ftoystore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjnunemaker%2Ftoystore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjnunemaker%2Ftoystore/lists"}