Services === A LHS::Service makes data available using multiple endpoints. ![Service](service.jpg) ## Convention Please store all defined services in `app/services` as they are autoloaded from this directory. ## Endpoints You setup a service by configure one or multiple backend endpoints. You can also add request options for an endpoint (see following example). ```ruby class Feedback < LHS::Service endpoint ':datastore/v2/content-ads/:campaign_id/feedbacks' endpoint ':datastore/v2/feedbacks', cache: true, cache_expires_in: 1.day end ``` If you try to setup a service with clashing endpoints it will immediately raise an exception. ```ruby class Feedback < LHS::Service endpoint ':datastore/v2/reviews' endpoint ':datastore/v2/feedbacks' end # raises: Clashing endpoints. ``` ## Find multiple records You can query the services by using `where`. ```ruby Feedback.where(has_reviews: true) #> ``` This uses the `:datastore/v2/feedbacks` endpoint, cause `:campaign_id` was not provided. In addition it would add `?has_reviews=true` to the get parameters. ```ruby Feedback.where(campaign_id: 'fq-a81ngsl1d') #> ``` Uses the `:datastore/v2/content-ads/:campaign_id/feedbacks` endpoint. → [Read more about collections](collections.md) ## Find single records `find` finds a unique item by uniqe identifier (usualy id). If no record is found an error is raised. ```ruby Feedback.find('z12f-3asm3ngals') #> ``` `find` can also be used to find a single uniqe item with parameters: ```ruby Feedback.find(campaign_id: 123, id: 456) ``` `find_by` finds the first record matching the specified conditions. If no record is found, `nil` is returned. `find_by!` raises LHC::NotFound if nothing was found. ```ruby Feedback.find_by(id: 'z12f-3asm3ngals') #> Feedback.find_by(id: 'doesntexist') # nil ``` `first` is a alias for finding the first of a service without parameters. ```ruby Feedback.first ``` If no record is found, `nil` is returned. `first!` raises LHC::NotFound if nothing was found. → [Read more about items](items.md) ## Batch processing ** Be carefull using methods for batch processing. They could result in a lot of HTTP requests! ** `all` fetches all records from the backend by doing multiple requests if necessary. ```ruby data = Feedback.all #> data.count # 998 data.total # 998 ``` → [Read more about collections](collections.md) `find_each` is a more fine grained way to process single records that are fetched in batches. ```ruby Feedback.find_each(start: 50, batch_size: 20, params: { has_reviews: true }) do |feedback| # Iterates over each record. Starts with record nr. 50 and fetches 20 records each batch. feedback #> end ``` `find_in_batches` is used by `find_each` and processes batches. ```ruby Feedback.find_in_batches(start: 50, batch_size: 20, params: { has_reviews: true }) do |feedbacks| # Iterates over multiple records (batch size is 20). Starts with record nr. 50 and fetches 20 records each batch. feedbacks #> end ``` ## Create records ```ruby feedback = Feedback.create( recommended: true, source_id: 'aaa', content_ad_id: '1z-5r1fkaj' ) #> ``` When creation fails, the object contains errors in its `errors` attribute: ```ruby feedback.errors # feedback.errors.include?(:ratings) # true feedback.errors[:ratings] # ['REQUIRED_PROPERTY_VALUE'] record.errors.messages # {:ratings=>["REQUIRED_PROPERTY_VALUE"], :recommended=>["REQUIRED_PROPERTY_VALUE"]} record.errors.message # ratings must be set when review or name or review_title is set | The property value is required; it cannot be null, empty, or blank." ``` ## Build new records Build and persist new items from scratch. ```ruby feedback = Feedback.build(recommended: true) feedback.save ``` → [Read more about items](items.md) ## Include linked resources A service lets you specify in advance all the linked resources that you want to include in the results. With includes, a service ensures that all matching and explicitly linked resources are loaded and merged. The implementation is heavily influenced by [http://guides.rubyonrails.org/active_record_querying](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations) and you should read it to understand this feature in all its glory. ### One-Level `includes` ```ruby # a claim has a localch_account claims = Claims.includes(:localch_account).where(place_id: 'huU90mB_6vAfUdVz_uDoyA') claims.first.localch_account.email # 'test@email.com' ``` * [see the JSON without include](examples/claim_no_include.json) * [see the JSON with include](examples/claim_with_include.json) ### Two-Level `includes` ```ruby # a feedback has a campaign, which has an entry feedbacks = Feedback.includes(campaign: :entry).where(has_reviews: true) feedbacks.first.campaign.entry.name # 'Casa Ferlin' ``` ### Known services are used to request linked resources When including linked resources with `includes`, known/defined services and endpoints are used to make those requests. That also means that options for endpoints of linked resources are applied when requesting those in addition. This enables you to include protected resources (e.g. OAuth) as endpoint options for oauth authentication get applied. The [Auth Inteceptor](https://github.com/local-ch/lhc-core-interceptors#auth-interceptor) from [lhc-core-interceptors](https://github.com/local-ch/lhc-core-interceptors) is used to configure the following endpoints. ```ruby class Favorite < LHS::Service endpoint ':datastore/:user_id/favorites', auth: { bearer: -> { bearer_token } } endpoint ':datastore/:user_id/favorites/:id', auth: { bearer: -> { bearer_token } } end class Place < LHS::Service endpoint ':datastore/v2/places', auth: { bearer: -> { bearer_token } } endpoint ':datastore/v2/places/:id', auth: { bearer: -> { bearer_token } } end Favorite.includes(:place).where(user_id: current_user.id) # Will include places and applies endpoint options to authenticate the request. ``` ## Map data To influence how data is accessed/provied, you can use mapping to either map deep nested data or to manipulate data when its accessed: ```ruby class LocalEntry < LHS::Service endpoint ':datastore/v2/local-entries' map :name, ->(entry){ entry.addresses.first.business.identities.first.name } end ```