{"id":13483155,"url":"https://github.com/IFTTT/kashmir","last_synced_at":"2025-03-27T13:33:41.522Z","repository":{"id":30192894,"uuid":"33743688","full_name":"IFTTT/kashmir","owner":"IFTTT","description":"Kashmir is a Ruby DSL that makes serializing and caching objects a snap.","archived":true,"fork":false,"pushed_at":"2023-10-11T20:40:07.000Z","size":232,"stargazers_count":267,"open_issues_count":2,"forks_count":12,"subscribers_count":43,"default_branch":"main","last_synced_at":"2025-03-19T11:45:51.626Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://ifttt.github.io","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/IFTTT.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2015-04-10T18:18:04.000Z","updated_at":"2024-07-10T03:11:09.000Z","dependencies_parsed_at":"2024-01-05T21:58:39.338Z","dependency_job_id":null,"html_url":"https://github.com/IFTTT/kashmir","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IFTTT%2Fkashmir","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IFTTT%2Fkashmir/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IFTTT%2Fkashmir/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IFTTT%2Fkashmir/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IFTTT","download_url":"https://codeload.github.com/IFTTT/kashmir/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245854866,"owners_count":20683428,"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-07-31T17:01:08.680Z","updated_at":"2025-03-27T13:33:41.246Z","avatar_url":"https://github.com/IFTTT.png","language":"Ruby","readme":"[![Open Source at IFTTT](http://ifttt.github.io/images/open-source-ifttt.svg)](http://ifttt.github.io)\n\n![Kashmir](https://raw.githubusercontent.com/IFTTT/kashmir/images/images/kashmirbanner.jpg?token=AAIf5wn0aFvxx1oNOO6GVw7SO4vENFW4ks5VuSaLwA%3D%3D \"Kashmir\")\n\nKashmir is a Ruby DSL that makes serializing and caching objects a snap.\n\nKashmir allows you to describe representations of Ruby objects. It will generate hashes from these Ruby objects using the representation and dependency tree that you specify.\n\n`Kashmir::ActiveRecord` will also optimize and try to balance `ActiveRecord` queries so your application hits the database as little as possible.\n\n`Kashmir::Caching` builds a dependency tree for complex object representations and caches each level of this tree separately. Kashmir will do so by creating cache views of each level as well as caching a complete tree.\nThe caching engine is smart enough to fill holes in the cache tree with fresh data from your data store.\n\nCombine `Kashmir::Caching` + `Kashmir::ActiveRecord` for extra awesomeness.\n\n### Example:\n\nFor example, a `Person` with `name` and `age` attributes:\n```ruby\n  class Person\n    include Kashmir\n    \n    def initialize(name, age)\n      @name = name\n      @age = age\n    end\n    \n    representations do\n      rep :name\n      rep :age\n    end\n  end\n```\ncould be represented as:\n```\n{ name: 'Netto Farah', age: 26 }\n```\n\nRepresenting an object is as simple as:\n\n1. Add `include Kashmir` to the target class.\n2. Whitelist all the fields you want to include in a representation.\n\n```ruby\n# Add fields and methods you want to be visible to Kashmir\nrepresentations do\n  rep(:name)\n  rep(:age)\nend\n```\n\n3. Instantiate an object and `#represent` it.\n```ruby\n# Pass in an array with all the fields you want included\nPerson.new('Netto Farah', 26).represent([:name, :age]) \n =\u003e {:name=\u003e\"Netto Farah\", :age=\u003e\"26\"} \n```\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'kashmir'\n```\n\nAnd then execute:\n\n    $ bundle\n\n## Usage\nKashmir is better described with examples.\n\n### Basic Representations\n\n#### Describing an Object\nOnly whitelisted fields can be represented by Kashmir.\nThis is done so sensitive fields (like passwords) cannot be accidentally exposed to clients.\n\n``` ruby\nclass Recipe \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    rep(:title)\n    rep(:preparation_time)\n  end\nend\n```\n\nInstantiate a `Recipe`:\n```ruby\nrecipe = Recipe.new(title: 'Beef Stew', preparation_time: 60)\n```\n\nKashmir automatically adds a `#represent` method to every instance of `Recipe`.\n`#represent` takes an `Array` with all the fields you want as part of your representation.\n\n```ruby\nrecipe.represent([:title, :preparation_time])\n=\u003e { title: 'Beef Stew', preparation_time: 60 }\n```\n#### Calculated Fields\nYou can represent any instance variable or method (basically anything that returns `true` for `respond_to?`).\n``` ruby\nclass Recipe \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    rep(:title)\n    rep(:num_steps)\n  end\n  \n  def num_steps\n    steps.size\n  end\nend\n```\n\n```ruby\nRecipe.new(title: 'Beef Stew', steps: ['chop', 'cook']).represent([:title, :num_steps])\n=\u003e { title: 'Beef Stew', num_steps: 2 }\n```\n\n### Nested Representations\nYou can nest Kashmir objects to represent complex relationships between your objects.\n```ruby\nclass Recipe \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    rep(:title)\n    rep(:chef)\n  end\nend\n\nclass Chef \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    base([:name])\n  end\nend\n```\n\nWhen you create a representation, nest hashes to create nested representations.\n```ruby\nnetto = Chef.new(name: 'Netto Farah')\nbeef_stew = Recipe.new(title: 'Beef Stew', chef: netto)\n\nbeef_stew.represent([:title, { :chef =\u003e [ :name ] }])\n=\u003e {\n  :title =\u003e \"Beef Stew\",\n  :chef =\u003e {\n    :name =\u003e 'Netto Farah'\n  }\n}\n```\nNot happy with this syntax? Check out `Kashmir::DSL` or `Kashmir::InlineDSL` for prettier code.\n\n#### Base Representations\nAre you tired of repeating the same fields over and over?\nYou can create a base representation of your objects, so Kashmir returns basic fields automatically.\n```ruby\nclass Recipe\n  include Kashmir\n  \n  representations do\n    base [:title, :preparation_time]\n    rep :num_steps\n    rep :chef\n  end\nend\n```\n`base(...)` takes an array with the fields you want to return on every representation of a given class.\n\n```ruby\nbrisket = Recipe.new(title: 'BBQ Brisket', preparation_time: 'a long time')\nbrisket.represent()\n=\u003e { :title =\u003e 'BBQ Brisket', :preparation_time =\u003e 'a long time' }\n```\n\n### Complex Representations\nYou can nest as many Kashmir objects as you want.\n```ruby\nclass Recipe \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    base [:title]\n    rep :chef\n  end\nend\n\nclass Chef \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    base :name\n    rep :restaurant\n  end\nend\n\nclass Restaurant \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    base [:name]\n    rep :rating\n  end\nend\n```\n\n```ruby\nbbq_joint = Restaurant.new(name: \"Netto's BBQ Joint\", rating: '5 Stars')\nnetto = Chef.new(name: 'Netto', restaurant: bbq_joint)\nbrisket = Recipe.new(title: 'BBQ Brisket', chef: netto)\n\nbrisket.represent([\n  :chef =\u003e [\n    { :restaurant =\u003e [ :rating ] }\n  ]\n])\n\n=\u003e {\n  title: 'BBQ Brisket',\n  chef: {\n    name: 'Netto',\n    restaurant: {\n      name: \"Netto's BBQ Joint\",\n      rating: '5 Stars'\n    }\n  }\n}\n\n```\n\n\n### Collections\nArrays of Kashmir objects work the same way as any other Kashmir representations.\nKashmir will augment `Array` with `#represent` that will represent every item in the array.\n\n```ruby\nclass Ingredient \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    rep(:name)\n    rep(:quantity)\n  end\nend\n\nclass ClassyRecipe \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    rep(:title)\n    rep(:ingredients)\n  end\nend\n```\n```ruby\nomelette = ClassyRecipe.new(title: 'Omelette Du Fromage')\nomelette.ingredients = [\n  Ingredient.new(name: 'Egg', quantity: 2),\n  Ingredient.new(name: 'Cheese', quantity: 'a lot!')\n]\n```\nJust describe your `Array` representations like any regular nested representation.\n```ruby\nomelette.represent([:title, { \n    :ingredients =\u003e [ :name, :quantity ]\n  }\n])\n```\n```ruby\n=\u003e {\n  title: 'Omelette Du Fromage',\n  ingredients: [\n    { name: 'Egg', quantity: 2 },\n    { name: 'Cheese', quantity: 'a lot!' }\n  ]\n}\n```\n### `Kashmir::Dsl`\nPassing arrays and hashes around can be very tedious and lead to duplication.\n`Kashmir::Dsl` allows you to create your own representers/decorators so you can keep your logic in one place and make way more expressive.\n\n```ruby\nclass Recipe \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    rep(:title)\n    rep(:num_steps)\n  end\nend\n\nclass RecipeRepresenter\n  include Kashmir::Dsl\n\n  prop :title\n  prop :num_steps\nend\n```\n\nAll you need to do is include `Kashmir::Dsl` in any ruby class. Every call to `prop(field_name)` will translate directly into just adding an extra field in the representation array.\n\n\nIn this case, `RecipeRepresenter` will translate directly to `[:title, :num_steps]`.\n\n```ruby\nbrisket = Recipe.new(title: 'BBQ Brisket', num_steps: 2)\nbrisket.represent(RecipePresenter)\n\n=\u003e  { title: 'BBQ Brisket', num_steps: 2 }\n```\n#### Embedded Representers\nIt is also possible to define nested representers with `embed(:property_name, RepresenterClass)`.\n\n```ruby\nclass RecipeWithChefRepresenter\n  include Kashmir::Dsl\n\n  prop :title\n  embed :chef, ChefRepresenter\nend\n\nclass ChefRepresenter\n  include Kashmir::Dsl\n  \n  prop :full_name\nend\n```\nKashmir will inline these classes and return a raw Kashmir description.\n```ruby\nRecipeWithChefRepresenter.definitions == [ :title, { :chef =\u003e [ :full_name ] }]\n=\u003e true\n```\nRepresenting the objects will work just as before.\n```ruby\nchef = Chef.new(first_name: 'Netto', last_name: 'Farah')\nbrisket = Recipe.new(title: 'BBQ Brisket', chef: chef)\n\nbrisket.represent(RecipeWithChefRepresenter)\n \n=\u003e {\n  title: 'BBQ Brisket',\n  chef: {\n    full_name: 'Netto Farah'\n  }\n}\n```\n#### Inline Representers\nYou don't necessarily need to define a class for every nested representation.\n```ruby\nclass RecipeWithInlineChefRepresenter\n  include Kashmir::Dsl\n\n  prop :title\n\n  inline :chef do\n    prop :full_name\n  end\nend\n```\nUsing `inline(:property_name, \u0026block)` will work the same way as `embed`. Except that you can now define short representations using ruby blocks. Leading us to our next topic.\n\n### `Kashmir::InlineDsl`\n`Kashmir::InlineDsl` sits right in between raw representations and Representers. It reads much better than arrays of hashes and provides the expressiveness of `Kashmir::Dsl` without all the ceremony.\n\nIt works with every feature from `Kashmir::Dsl` and allows you to define quick inline descriptions for your `Kashmir` objects.\n\n```ruby\nclass Recipe \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    rep(:title)\n    rep(:num_steps)\n  end\nend\n```\nJust call `#represent_with(\u0026block)` on any `Kashmir` object and use the `Kashmir::Dsl` syntax.\n```ruby\nbrisket = Recipe.new(title: 'BBQ Brisket', num_steps: 2)\n\nbrisket.represent_with do\n  prop :title\n  prop :num_steps\nend\n\n=\u003e { title: 'BBQ Brisket', num_steps: 2 }\n```\n\n#### Nested Inline Representations\nYou can nest inline representations using `inline(:field, \u0026block)` the same way we did with `Kashmir::Dsl`.\n\n```ruby\nclass Ingredient \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    rep(:name)\n    rep(:quantity)\n  end\nend\n\nclass ClassyRecipe \u003c OpenStruct\n  include Kashmir\n\n  representations do\n    rep(:title)\n    rep(:ingredients)\n  end\nend\n```\n```ruby\nomelette = ClassyRecipe.new(title: 'Omelette Du Fromage')\nomelette.ingredients = [\n  Ingredient.new(name: 'Egg', quantity: 2),\n  Ingredient.new(name: 'Cheese', quantity: 'a lot!')\n]\n```\nJust call `#represent_with(\u0026block)` and start nesting other inline representations.\n```ruby\nomelette.represent_with do\n  prop :title\n  inline :ingredients do\n    prop :name\n    prop :quantity\n  end\nend\n\n=\u003e {\n  title: 'Omelette Du Fromage',\n  ingredients: [\n    { name: 'Egg', quantity: 2 },\n    { name: 'Cheese', quantity: 'a lot!' }\n  ]\n}\n```\n\nInline representations can become lengthy and confusing over time.\nIf you find yourself nesting more than two levels or including more than 3 or 4 fields per level consider creating Representers with `Kashmir::Dsl`.\n\n### `Kashmir::ActiveRecord`\nKashmir works just as well with ActiveRecord. `ActiveRecord::Relation`s can be used as Kashmir representations just as any other classes.\n\nKashmir will attempt to preload every `ActiveRecord::Relation` defined as representations automatically by using `ActiveRecord::Associations::Preloader`. This will guarantee that you don't run into N+1 queries while representing collections and dependent objects.\n\nHere's an example of how Kashmir will attempt to optimize database queries:\n\n```ruby\nActiveRecord::Schema.define do\n  create_table :recipes, force: true do |t|\n    t.column :title, :string\n    t.column :num_steps, :integer\n    t.column :chef_id, :integer\n  end\n  \n  create_table :chefs, force: true do |t|\n    t.column :name, :string\n  end\nend\n```\n```ruby\nmodule AR\n  class Recipe \u003c ActiveRecord::Base\n    include Kashmir\n\n    belongs_to :chef\n\n    representations do\n      rep :title\n      rep :chef\n    end\n  end\n\n  class Chef \u003c ActiveRecord::Base\n    include Kashmir\n\n    has_many :recipes\n\n    representations do\n      rep :name\n      rep :recipes\n    end\n  end\nend\n```\n\n```ruby\nAR::Chef.all.each do |chef|\n  chef.recipes.to_a\nend\n```\nwill generate\n```sql\nSELECT * FROM chefs\nSELECT \"recipes\".* FROM \"recipes\" WHERE \"recipes\".\"chef_id\" = ?\nSELECT \"recipes\".* FROM \"recipes\" WHERE \"recipes\".\"chef_id\" = ?\n```\n\nWith Kashmir:\n```ruby\nAR::Chef.all.represent([:recipes])\n```\n```sql\nSELECT \"chefs\".* FROM \"chefs\"\nSELECT \"recipes\".* FROM \"recipes\" WHERE \"recipes\".\"chef_id\" IN (1, 2)\n```\n\nFor more examples, check out: https://github.com/IFTTT/kashmir/blob/master/test/activerecord_tricks_test.rb\n\n### `Kashmir::Caching` (Experimental)\nCaching is the best feature in Kashmir.\nThe `Kashmir::Caching` module will cache every level of the dependency tree Kashmir generates when representing an object.\n\n![Dependency Tree](https://raw.githubusercontent.com/IFTTT/kashmir/images/images/kashmir.png?token=AAIf57rtAVfFPENYmWfBJ9nhZOmbFs1qks5VuVFOwA%3D%3D \"Dependency Tree\")\n\nAs you can see in the image above, Kashmir will build a dependency tree of the representation.\nIf you have Caching on, Kashmir will:\n\n- Build a cache key for each individual object (green)\n- Wrap complex dependencies into their on cache key (blue and pink)\n- Wrap the whole representation into one unique cache key (red)\n\nEach layer gets its own cache keys which can be expired at different times.\nKashmir will also be able to fill in blanks in the dependency tree and fetch missing objects individually.\n\nCaching is turned off by default, but you can use one of the two available implementations.\n\n- [In Memory Caching] https://github.com/IFTTT/kashmir/blob/master/lib/kashmir/plugins/memory_caching.rb\n- [Memcached] https://github.com/IFTTT/kashmir/blob/master/lib/kashmir/plugins/memcached_caching.rb\n\nYou can also build your own custom caching engine by following the `NullCaching` protocol available at:\nhttps://github.com/IFTTT/kashmir/blob/master/lib/kashmir/plugins/null_caching.rb\n\n#### Enabling `Kashmir::Caching`\n##### In Memory\n```ruby\nKashmir.init(\n  cache_client: Kashmir::Caching::Memory.new\n)\n```\n\n##### With Memcached\n```ruby\nrequire 'kashmir/plugins/memcached_caching'\n\nclient = Dalli::Client.new(url, namespace: 'kashmir', compress: true)\ndefault_ttl = 5.minutes\n\nKashmir.init(\n  cache_client: Kashmir::Caching::Memcached.new(client, default_ttl)\n)\n```\n\nFor more advanced examples, check out: https://github.com/IFTTT/kashmir/blob/master/test/caching_test.rb\n\n## Contributing\n\n1. Fork it ( https://github.com/[my-github-username]/kashmir/fork )\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 a new Pull Request\n\n\n","funding_links":[],"categories":["Ruby","Caching"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FIFTTT%2Fkashmir","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FIFTTT%2Fkashmir","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FIFTTT%2Fkashmir/lists"}