diff --git a/lib/json_api_client/errors.rb b/lib/json_api_client/errors.rb index 5d8d6aa6..8cf41d51 100644 --- a/lib/json_api_client/errors.rb +++ b/lib/json_api_client/errors.rb @@ -11,6 +11,12 @@ def initialize(env, msg = nil) class ClientError < ApiError end + class ResourceImmutableError < StandardError + def initialize(msg = 'Resource immutable') + super msg + end + end + class AccessDenied < ClientError end diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index dd1f187d..ff47f849 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -40,6 +40,11 @@ class Resource instance_accessor: false class_attribute :add_defaults_to_changes, instance_writer: false + + class_attribute :_immutable, + instance_writer: false, + default: false + self.primary_key = :id self.parser = Parsers::Parser self.paginator = Paginating::Paginator @@ -94,6 +99,18 @@ def type table_name end + # Indicates whether this resource is mutable or immutable; + # by default, all resources are mutable. + # + # @return [Boolean] + def immutable(flag = true) + self._immutable = flag + end + + def inherited(subclass) + subclass._immutable = false + end + # Specifies the relative path that should be used for this resource; # by default, this is inferred from the resource class name. # @@ -215,6 +232,11 @@ def route_formatter # @option [Symbol] :on One of [:collection or :member] to decide whether it's a collect or member method # @option [Symbol] :request_method The request method (:get, :post, etc) def custom_endpoint(name, options = {}) + if _immutable + request_method = options.fetch(:request_method, :get).to_sym + raise JsonApiClient::Errors::ResourceImmutableError if request_method != :get + end + if :collection == options.delete(:on) collection_endpoint(name, options) else @@ -440,6 +462,7 @@ def valid?(context = nil) # @return [Boolean] Whether or not the save succeeded def save return false unless valid? + raise JsonApiClient::Errors::ResourceImmutableError if _immutable self.last_result_set = if persisted? self.class.requestor.update(self) @@ -471,6 +494,8 @@ def save # # @return [Boolean] Whether or not the destroy succeeded def destroy + raise JsonApiClient::Errors::ResourceImmutableError if _immutable + self.last_result_set = self.class.requestor.destroy(self) if last_result_set.has_errors? fill_errors diff --git a/test/unit/resource_test.rb b/test/unit/resource_test.rb index 5af0c5b1..088bdff0 100644 --- a/test/unit/resource_test.rb +++ b/test/unit/resource_test.rb @@ -95,4 +95,14 @@ def test_default_params_overrideable assert_equal(article.type, 'Story') end + def test_immutable + Article.immutable(true) + article = Article.new(type: 'Story') + assert_raises JsonApiClient::Errors::ResourceImmutableError do + article.save + end + ensure + Article.immutable(false) + end + end