{"id":13880029,"url":"https://github.com/contentful/contentful_model","last_synced_at":"2025-04-06T01:10:42.013Z","repository":{"id":23974209,"uuid":"27356928","full_name":"contentful/contentful_model","owner":"contentful","description":"A lightweight wrapper around the Contentful api gem, to make it behave more like ActiveRecord","archived":false,"fork":false,"pushed_at":"2023-10-26T13:42:56.000Z","size":283,"stargazers_count":44,"open_issues_count":12,"forks_count":42,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-03-30T00:08:37.994Z","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/contentful.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-12-01T01:18:55.000Z","updated_at":"2023-11-10T19:32:34.000Z","dependencies_parsed_at":"2022-08-22T06:50:57.756Z","dependency_job_id":"8f83418f-c0d0-4c15-a2bc-09829dde2541","html_url":"https://github.com/contentful/contentful_model","commit_stats":{"total_commits":163,"total_committers":26,"mean_commits":6.269230769230769,"dds":0.5276073619631902,"last_synced_commit":"741a57eabcd95194393496966e8d64e45188d7b1"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contentful%2Fcontentful_model","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contentful%2Fcontentful_model/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contentful%2Fcontentful_model/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/contentful%2Fcontentful_model/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/contentful","download_url":"https://codeload.github.com/contentful/contentful_model/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247419861,"owners_count":20936012,"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-06T08:02:44.291Z","updated_at":"2025-04-06T01:10:41.994Z","avatar_url":"https://github.com/contentful.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# ContentfulModel\n\nThis is a thin wrapper around the [Contentful Delivery SDK](https://github.com/contentful/contentful.rb) and [Contentful Management SDK](https://github.com/contentful/contentful-management.rb) api client libraries.\n\nIt allows you to inherit from `ContentfulModel::Base` and specify the content type id, and optionally, fields to coerce in a specific way.\n\nNote that this library doesn't allow you to save changes to your models back to Contentful. We need to use the Contentful Management API for that. Pull requests welcome!\n\n## What is Contentful?\n\n[Contentful](https://www.contentful.com) provides a content infrastructure for digital teams to power content in websites, apps, and devices. Unlike a CMS, Contentful was built to integrate with the modern software stack. It offers a central hub for structured content, powerful management and delivery APIs, and a customizable web app that enable developers and content creators to ship digital products faster.\n\n# Usage\n\n## Configure ContentfulModel\n\nConfigure ContentfulModel with a block. In a Rails app this is best done in an initializer:\n\n```ruby\nContentfulModel.configure do |config|\n  config.access_token = \"your access token in here\" # Required\n  config.preview_access_token = \"your preview token in here\" # Optional - required if you want to use the preview API\n  config.management_token = \"your management token in here\" # Optional - required if you want to update or create content\n  config.space = \"your space id in here\" # Required\n  config.environment = \"master\" # Optional - defaults to 'master'\n  config.default_locale = \"en-US\" # Optional - defaults to 'en-US'\n  config.options = { # Optional\n    # Extra options to send to the Contentful::Client and Contentful::Management::Client\n    # See https://github.com/contentful/contentful.rb#configuration\n\n    # Optional:\n    # Use `delivery_api` and `management_api` keys to limit to what API the settings\n    # will apply. Useful because Delivery API is usually visitor facing, while Management\n    # is used in background tasks that can run much longer. For example:\n    delivery_api: {\n      timeout_read: 6\n    },\n    management_api: {\n      timeout_read: 100\n    }\n  }\nend\n\n```\n\nIt is important to set the `default_locale` to match the one set in your Contentful settings, otherwise the fields will have no content.\n\n## Create a model class\n\nCreate a class which inherits from `ContentfulModel::Base`.\n\n```ruby\nclass Foo \u003c ContentfulModel::Base\n   self.content_type_id = \"content type id for this model\"\nend\n```\n\nContentfulModel takes care of setting instance variables for each field in your model. You can optionally coerce fields to the right format - for example dates:\n\n```ruby\nclass Foo \u003c ContentfulModel::Base\n   self.content_type_id = \"content type id for this model\"\n\n   coerce_field birthday: :Date\n   coerce_field store_id: :Integer\nend\n```\nSee a list of the available coercions [here](https://github.com/contentful/contentful.rb/blob/master/lib/contentful/field.rb#L9-L22).\n\n## Queries and Searching\nContentfulModel allows you to chain queries, like ActiveRecord. The options are as follows.\n\n### `first()`\nReturns the first entry for your content type.\n\n### `all()`\nReturns all entries of a particular content type. Requires `load()` to be called at the end of the chain.\n\n```\nFoo.all.load\n```\n\n### `params([hash])`\nAllows you to send any arbitrary query to Contentful.\n\n### `offset([integer])`\n(Also aliased as `skip()`). Allows you to specify an offset from the start of the returned set. Requires `load()` at the end of the chain.\n\n```\nFoo.all.offset(2).load\n```\n\n### `limit([integer])`\nLimits the amount of returned entries (minimum 1, maximum 1000, default is 100).\n\n### `paginate(page = 1, per_page = 100, order_field = 'sys.updatedAt', additional_options = {})`\nFetches the requested entry page. `additional_options` allows you to send more specific query parameters.\n\n### `each_page(per_page = 100, order_field = 'sys.updatedAt', additional_options = {}, \u0026block)`\nAllows you to execute the given block over each page for your content type. It automatically does pagination for you.\n\n### `each_entry(per_page = 100, order_field = 'sys.updatedAt', additional_options = {}, \u0026block)`\nSame as `each_page` but iterates through every entry. It automatically does pagination for you.\n\n### `find([id])`\nReturns the entry of the content type you've called, matching the id you passed into the `find()` method. _Does not_ require `load()`.\n\n```ruby\nFoo.find(\"someidfromcontentful\")\n```\n\n### `find_by([hash])`\nAccepts a hash of options to include in the search, as similar as possible to ActiveRecord version. __Note__ that this doesn't work (and will throw an error) on fields which need the full-text search. This needs fixing. Requires load() to be called at the end of the chain.\n\n```ruby\nFoo.find_by(someField: [searchquery1, searchquery2], someOtherField: \"bar\").load\n```\n\nYou'll see from the example above that it accepts an array of search terms which will invoke an 'in' query.\n\n### `params({object})`\nIncludes the specified parameters in the Contentful API call.\n\n```ruby\nFoo.all.params({\"include\" =\u003e 3}).load\n```\n\n### `locale([string])`\nFetches the entries for a specific locale code, or all if `'*'` is sent.\n\n### `load_children([integer])`\nFetches nested links to the specified level.\n\n```ruby\nFoo.load_children(3).load\n```\n\n### `order([string, hash or array of strings])`\nSets the order for the executed query.\n\nFor example, to sort in reverse chronological order: `order(createdAt: :desc)`\n\n## Associations\nYou can specify associations between models in a similar way to ActiveRecord. There are some differences, though, so read on.\n\n### There's no `belongs_to`\nContentful allows you to create relationships between content types - so a parent content type can have one or many child entries. You can (and probably should) validate the content types in your child field - for example, a 'page' might have one entry of content type 'template'.\n\nHowever: it's not possible to assign an entry only once - so that means that every child entry might belong to more than one parent. So there isn't a `belongs_to` method because it wouldn't make any sense. Use `belongs_to_many` instead.\n\n(If you happen to accidentally declare `belongs_to` on a model, you'll get a `NotImplementedError`.\n\n### `has_one`\n\nDefine a `has_one` relationship on a parent model, just like you would for ActiveRecord. For example:\n\n\nA simple example:\n\n```ruby\nclass Article \u003c ContentfulModel::Base\n    has_one :author #author is the name of the field in contentful\nend\n```\n\nA more complex example, with a child model class that doesn't follow the name of the field on the parent.\n\n```ruby\nclass Page \u003c ContentfulModel::Base\n    has_one :template, class_name: \"PageTemplate\", inverse_of: :page #template is the name of the field in contentful\nend\n```\n\n`Page` will now have a method called `template` which returns the PageTemplate you have assigned in Contentful. Similarly, `Article` will have a method called `author` which returns the Author.\n\nProvided you've properly set up `belongs_to_many` on the other end of the relationship, you'll have a utility method on the child model called `page()`. This is the entity *which loaded the child*. In most cases this is pretty useful.\n\n### `has_many`\nUsing `has_many` is conceptually identical to `has_one`, instead of a single entity you'll receive an array.\n\n### `belongs_to_many`\nUse `belongs_to_many` on the other end of a `has_one` or `has_many` relationship.\n\nOur Article class above has a child called Author. The author will probably belong to many articles.\n\n(note: you could model this particular example the other way around)\n\n```ruby\nclass Author \u003c ContentfulModel::Base\n    belongs_to_many :articles\nend\n```\n\nUsing `belongs_to_many` gives you a couple of useful methods on the child. Using the Article example above:\n\n* `article()` - this is the parent article which called the child author. If you call Author.find() explicitly, this will be nil\n* `articles()` - this requires an API call, and will return a collection of articles which has this author as a child\n\n### `has_many_nested` - using self-referential content types to create a tree\nThis is a departure from the classic ActiveRecord syntax here, but pretty useful. There are probably situations where you want to have a content type which has children of the same type. For example, a [Error](http://www.errorstudio.co.uk) we often has a tree of pages for a website.\n\nIn this example let's assume you have a Page content type, which has a field called childPages in Contentful - this is referenced as child_pages in ContentfulModel.\n\nHere's how you'd set it up:\n\n```ruby\nclass Page \u003c ContentfulModel::Base\n    has_many_nested :child_pages\nend\n```\n\nThis calls `has_many` and `belongs_to_many` on the Page model, and gives you some useful instance methods.\n\n* `parent()` - the parent of the current Page\n* `children()` - the children of the current Page\n* `has_children?()` - returns `true` or `false` depending on whether this page has children\n* `root?()` - returns `true` or `false` depending on whether this Page is a root page (i.e. no parents)\n* `root()` - returns the root page, from this page's point of view\n* `ancestors()` - an `Enumerable` you can iterate over to get all the ancestors. Surprisingly quick.\n* `nested_children` - returns a nested hash of children\n* `nested_children_by(:field)` - takes the name of the field you want to return, and returns a hash of nested children by the field you specify. E.g. `nested_children_by(:slug)`.\n* `find_child_path_by(:field, \"thing-to-search\")` - returns an array of the child's parents. Useful for determining the ancestors of an entity you've called directly.\n* `all_child_paths_by(:field)` - return a 2d array of paths for all children. One of a couple of ways you can set up navigation.\n\nFrom this, you can:\n\n* Build up a tree from calls to the top-level entity (e.g. a navigation tree)\n* Reverse-iterate up the tree from a given page (e.g. breadcrumbs)\n\n#### Defining a root page\nYou can pass an optional second parameter into `has_many_nested` which means the class knows how to find its root:\n\n```ruby\nclass Page \u003c ContentfulModel::Base\n    has_many_nested :child_pages, root: -\u003e { Page.find(\"some_id\").first }\nend\n```\n\nAdding this second parameter defines a method called `root_page` on the class, so you can get the root easily. Your proc needs to return one object.\n\n### `add_entry_mapping` - required for eager-loading referenced classes\n\n(note: If you are using [contentful_rails](https://github.com/errorstudio/contentful_rails), it is automatically done by that gem.)\n\nYou may find that your entries are not mapped to your class when using `has_one` / `has_many`.\n\n```ruby\nclass Article \u003c ContentfulModel::Base\n  self.content_type_id = 'article'\nend\n\nclass Author \u003c ContentfulModel::Base\n  self.content_type_id = 'author'\n  \n  has_many :articles, class_name: 'Article'\nend\n\n[1] pry(main)\u003e Author.first.articles\n[\u003cContentful::Entry[article] id='1YyNsj0FcYuLEaxkvSW5s'\u003e]\n```\n\nYou should eager-load the class (in this example, `Article`) by writing `add_entry_mapping`.\n\n```ruby\nclass Article \u003c ContentfulModel::Base\n  self.content_type_id = 'article'\nend\n\nArticle.add_entry_mapping\n```\n\nIf you prefer to do this automatically, just call `add_entry_mapping` for all descendents of `ContentfulModel::Base`.\n(This is what contentful_rails is doing under the hood.)\n\n```ruby\n# Required if you are using contentful_model from Rails (typically inside initializers)\n# Remember calling this so that ContentfulModel::Base.descendents will not be empty\nRails.application.eager_load!\n\nContentfulModel::Base.descendents.each do |klass|\n  klass.send(:add_entry_mapping)\nend\n```\n\n## Preview mode\nYou might want to hit the preview API. Our [contentful_rails](https://github.com/errorstudio/contentful_rails) gem uses it, for example.\n\nProvided you've set a `preview_api_token` in the configuration block, it's dead easy. Just set `ContentfulModel.use_preview_api = true` before making calls.\n\n## Validations\n\nValidations are a simple way to check content before sending it back to the server on `#save`.\nThis is often useful when working on organizations with heavy CMA traffic, in which by using validations previous to hitting the API,\nwe can ensure that the API will only be hit when a successful request can be made.\n\nThere is a special case of validations, that are run when content is being loaded when running `::load`,\nfailing those validations will filter out that content from the response,\nyou can transform any of the following validations to an `:on_load` validation by appending `on_load: true` on the `::validate` filter.\nThis validations will also be run before `#save`.\n\nYou can skip validations on `#save` by calling it as `my_entry.save(true)` or `my_entry.save!`.\n\n### Inline Validations\nInline validations are a useful shorthand for creating validations, they can be added as a lambda function or a block. In any of the cases,\nthey must receive a single parameter and return a boolean.\n\n```ruby\nclass Product \u003c ContentfulModel::Base\n  # validation with a lambda\n  validate :price_is_positive, -\u003e (e) { e.price \u003e 0 }\n\n  # validation with a block\n  validate :category_is_not_empty do |e|\n    !e.category.empty?\n  end\n\n  # :on_load validation\n  validate :name_longer_than_3_characters, -\u003e (e) { e.name.size \u003e 3 }, on_load: true\nend\n```\n\nThe above example will run the `:price_is_positive` and `:category_is_not_empty` only on `#save` and `:name_is_longer_than_3_characters` both on `::load` and `#save`.\n\nWhen an inline validation fails, an error with the following structure will be added to `#errors`: `\"#{validation_alias}: validation not met\"`.\n\n### Class and Object based Validations\nIf inline validations aren't flexible enough for your needs, you can create class and object based validations.\nThese validations can return multiple errors. They must define a `#validate(entry)` method, which must return an array of errors or an empty array.\n\nClass validations are useful when the validation class doesn't require setup. For example:\n\n```ruby\nclass NameValidation\n  def validate(entry)\n    errors = []\n    errors \u003c\u003c \"Name not long enough\" if entry.name.size \u003c 3\n    errors \u003c\u003c \"Name not capitalized\" if entry.name != entry.name.capitalize\n    errors\n  end\nend\n\nclass Post \u003c ContentfulModel::Base\n  validate_with NameValidation\nend\n```\n\nObject validations are just like Class validations, with the exception that they can define a constructor in order to parameterize them. For example:\n\n```ruby\nclass FieldLengthValidation\n  def initialize(field_name, length)\n    @field_name = field_name\n    @length = length\n  end\n\n  def validate(entry)\n    errors = []\n    errors \u003c\u003c \"#{@field_name} less than #{@length} character/s long\" if entry.public_send(@field_name).size \u003c @length\n    errors\n  end\nend\n\nclass Post \u003c ContentfulModel::Base\n  validate_with FieldLengthValidation.new(:title, 10)\n  validate_with FieldLengthValidation.new(:content, 200)\nend\n```\n\nWith these validations, you can define more complex and combined validations for your models.\nAs with Inline validations, you can enable them to run on `::load` by appending `on_load: true` after the `::validate_with` filter.\n\n### PresenceOf - Suppressing unvalidated content in preview\nDraft content doesn't include values which didn't pass the validations on responses.\nThis content wouldn't be able to be saved on the CMA, and the WebApp keeps the state but doesn't save it to the API.\n\nTherefore, when using the content, we require a way to make sure that those values are present in the entries we intend to display,\nand be able to filter out entries that do not contain those values, so a filter with an ActiveRecord-like sintax is provided.\n\nThis validation is always run `:on_load`\n\n```ruby\nclass Page \u003c ContentfulModel::Base\n  validates_presence_of :slug, :title, :some_other_vital_field\nend\n```\n\nIf you've defined this in a class, any queries to the API will filter out entities which aren't valid. This applies to both relations (where you might get a collection), or searches.\n\n## Returning nil for fields which aren't defined\n\nIf an object is valid, but has content missing from a field, the Contentful API doesn't return the field. That means that you have to check manually for its existence to avoid raising a `NoMethodError`.\n\nWe decided it would be nice to be able to declare that certain fields should return nil, rather than raising an error. You can do that as follows:\n\n```ruby\nclass Page\n  return_nil_for_empty :content, :excerpt\nend\n```\n\nThis means you can check for `content.nil?` instead of rescueing from an error. Much nicer.\n\n## Updating Content\n\nWith the newly introduced support for Contentful's CMA, you can now create and update entries\n\n### Creating entries\n\nYou can create entries by doing:\n\n```ruby\nMyModel.create(my_field: 'some value')\n```\n\n### Saving entries\n\nJust call the `#save` method on your entries and it will get updated on Contentful\n\n```ruby\nmy_model.my_field = 'other value'\n\nmy_model.save\n```\n\n### Publishing entries\n\nYou can also publish directly by calling `#publish` on your models.\n\n```ruby\nmy_model.publish\n```\n\n## Assets\n\nAssets are wrapped in a `ContentfulModel::Asset` class, which has shorthand methods for all of the Image API options.\n\n```ruby\nurl = my_asset.resize(10, 20).rounded_corners(30).png_8bit.thumbnail_focused_on('face').load\n```\n\nYou can also perform searches on assets.\n\n```ruby\nassets = Asset.all('sys.updatedAt[gte]' =\u003e 2.days.ago)\n\nasset = Asset.find('asset_id')\n```\n\n## Content Migrations\n\nYou can also create and alter Content Types with the Migrations module.\n\nYou can use it as follows:\n\n```ruby\nclass CreateFooContentType \u003c ActiveRecord::Migration\n  include ContentfulModel::Migrations::Migration\n\n  def up\n    create_content_type('foo') do |ct|\n      ct.field('bar', :symbol)\n      ct.field('baz', :date)\n    end\n\n    add_content_type_field 'foo', 'foobar', :integer\n  end\n\n  def down\n    remove_content_type_field 'foo', 'foobar'\n  end\nend\n```\n\n# To Do\nThere are quite a few outstanding tasks:\n\n* Some tests :-)\n\n# Licence\nMIT - please see MIT-LICENCE in this repo.\n\n# Contributing\nPlease feel free to contribute. We would love your input.\n\n* Fork the repo\n* Make changes\n* Commit and make a PR :-)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcontentful%2Fcontentful_model","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcontentful%2Fcontentful_model","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcontentful%2Fcontentful_model/lists"}