<commit_before>require 'jsonapi/callbacks'

module JSONAPI
  class Resource
    include Callbacks

    @@resource_types = {}

    attr :context
    attr_reader :model

    define_jsonapi_resources_callbacks :create,
                                       :update,
                                       :remove,
                                       :save,
                                       :create_has_many_link,
                                       :replace_has_many_links,
                                       :create_has_one_link,
                                       :replace_has_one_link,
                                       :remove_has_many_link,
                                       :remove_has_one_link,
                                       :replace_fields

    def initialize(model, context = nil)
      @model = model
      @context = context
    end

    def id
      model.send(self.class._primary_key)
    end

    def is_new?
      id.nil?
    end

    def change(callback)
      if @changing
        run_callbacks callback do
          yield
        end
      else
        run_callbacks is_new? ? :create : :update do
          @changing = true
          run_callbacks callback do
            yield
          end

          save if @save_needed || is_new?
        end
      end
    end

    def remove
      run_callbacks :remove do
        _remove
      end
    end

    def create_has_many_links(association_type, association_key_values)
      change :create_has_many_link do
        _create_has_many_links(association_type, association_key_values)
      end
    end

    def replace_has_many_links(association_type, association_key_values)
      change :replace_has_many_links do
        _replace_has_many_links(association_type, association_key_values)
      end
    end

    def replace_has_one_link(association_type, association_key_value)
      change :replace_has_one_link do
        _replace_has_one_link(association_type, association_key_value)
      end
    end

    def remove_has_many_link(association_type, key)
      change :remove_has_many_link do
        _remove_has_many_link(association_type, key)
      end
    end

    def remove_has_one_link(association_type)
      change :remove_has_one_link do
        _remove_has_one_link(association_type)
      end
    end

    def replace_fields(field_data)
      change :replace_fields do
        _replace_fields(field_data)
      end
    end

    # Override this on a resource instance to override the fetchable keys
    def fetchable_fields
      self.class.fields
    end

    # Override this on a resource to customize how the associated records
    # are fetched for a model. Particularly helpful for authorization.
    def records_for(association_name, options = {})
      model.send association_name
    end

    private
    def save
      run_callbacks :save do
        _save
      end
    end

    def _save
      unless @model.valid?
        raise JSONAPI::Exceptions::ValidationErrors.new(@model.errors.messages)
      end

      saved = @model.save
      @save_needed = !saved
      saved
    end

    def _remove
      @model.destroy
    end

    def _create_has_many_links(association_type, association_key_values)
      association = self.class._associations[association_type]

      association_key_values.each do |association_key_value|
        related_resource = Resource.resource_for(self.class.module_path + association.type.to_s).find_by_key(association_key_value, context: @context)

        # ToDo: Add option to skip relations that already exist instead of returning an error?
        relation = @model.send(association.type).where(association.primary_key => association_key_value).first
        if relation.nil?
          @model.send(association.type) << related_resource.model
        else
          raise JSONAPI::Exceptions::HasManyRelationExists.new(association_key_value)
        end
      end
    end

    def _replace_has_many_links(association_type, association_key_values)
      association = self.class._associations[association_type]

      send("#{association.foreign_key}=", association_key_values)
      @save_needed = true
    end

    def _replace_has_one_link(association_type, association_key_value)
      association = self.class._associations[association_type]

      send("#{association.foreign_key}=", association_key_value)
      @save_needed = true
    end

    def _remove_has_many_link(association_type, key)
      association = self.class._associations[association_type]

      @model.send(association.type).delete(key)
    end

    def _remove_has_one_link(association_type)
      association = self.class._associations[association_type]

      send("#{association.foreign_key}=", nil)
      @save_needed = true
    end

    def _replace_fields(field_data)
      field_data[:attributes].each do |attribute, value|
        begin
          send "#{attribute}=", value
          @save_needed = true
        rescue ArgumentError
          # :nocov: Will be thrown if an enum value isn't allowed for an enum. Currently not tested as enums are a rails 4.1 and higher feature
          raise JSONAPI::Exceptions::InvalidFieldValue.new(attribute, value)
          # :nocov:
        end
      end

      field_data[:has_one].each do |association_type, value|
        if value.nil?
          remove_has_one_link(association_type)
        else
          replace_has_one_link(association_type, value)
        end
      end if field_data[:has_one]

      field_data[:has_many].each do |association_type, values|
        replace_has_many_links(association_type, values)<commit_msg>Return a result from methods called by operations<commit_after>require 'jsonapi/callbacks'

module JSONAPI
  class Resource
    include Callbacks

    @@resource_types = {}

    attr :context
    attr_reader :model

    define_jsonapi_resources_callbacks :create,
                                       :update,
                                       :remove,
                                       :save,
                                       :create_has_many_link,
                                       :replace_has_many_links,
                                       :create_has_one_link,
                                       :replace_has_one_link,
                                       :remove_has_many_link,
                                       :remove_has_one_link,
                                       :replace_fields

    def initialize(model, context = nil)
      @model = model
      @context = context
    end

    def id
      model.send(self.class._primary_key)
    end

    def is_new?
      id.nil?
    end

    def change(callback)
      if @changing
        run_callbacks callback do
          yield
        end
      else
        run_callbacks is_new? ? :create : :update do
          @changing = true
          result = nil
          run_callbacks callback do
            result = yield
          end

          if @save_needed || is_new?
            save
          else
            return result
          end
        end
      end
    end

    def remove
      run_callbacks :remove do
        _remove
      end
    end

    def create_has_many_links(association_type, association_key_values)
      change :create_has_many_link do
        _create_has_many_links(association_type, association_key_values)
      end
    end

    def replace_has_many_links(association_type, association_key_values)
      change :replace_has_many_links do
        _replace_has_many_links(association_type, association_key_values)
      end
    end

    def replace_has_one_link(association_type, association_key_value)
      change :replace_has_one_link do
        _replace_has_one_link(association_type, association_key_value)
      end
    end

    def remove_has_many_link(association_type, key)
      change :remove_has_many_link do
        _remove_has_many_link(association_type, key)
      end
    end

    def remove_has_one_link(association_type)
      change :remove_has_one_link do
        _remove_has_one_link(association_type)
      end
    end

    def replace_fields(field_data)
      change :replace_fields do
        _replace_fields(field_data)
      end
    end

    # Override this on a resource instance to override the fetchable keys
    def fetchable_fields
      self.class.fields
    end

    # Override this on a resource to customize how the associated records
    # are fetched for a model. Particularly helpful for authorization.
    def records_for(association_name, options = {})
      model.send association_name
    end

    private
    def save
      run_callbacks :save do
        _save
      end
    end

    def _save
      unless @model.valid?
        raise JSONAPI::Exceptions::ValidationErrors.new(@model.errors.messages)
      end

      saved = @model.save
      @save_needed = !saved

      return :default
    end

    def _remove
      @model.destroy
    end

    def _create_has_many_links(association_type, association_key_values)
      association = self.class._associations[association_type]

      association_key_values.each do |association_key_value|
        related_resource = Resource.resource_for(self.class.module_path + association.type.to_s).find_by_key(association_key_value, context: @context)

        # ToDo: Add option to skip relations that already exist instead of returning an error?
        relation = @model.send(association.type).where(association.primary_key => association_key_value).first
        if relation.nil?
          @model.send(association.type) << related_resource.model
        else
          raise JSONAPI::Exceptions::HasManyRelationExists.new(association_key_value)
        end
      end

      return :default
    end

    def _replace_has_many_links(association_type, association_key_values)
      association = self.class._associations[association_type]

      send("#{association.foreign_key}=", association_key_values)
      @save_needed = true

      return :default
    end

    def _replace_has_one_link(association_type, association_key_value)
      association = self.class._associations[association_type]

      send("#{association.foreign_key}=", association_key_value)
      @save_needed = true
    end

    def _remove_has_many_link(association_type, key)
      association = self.class._associations[association_type]

      @model.send(association.type).delete(key)

      return :default
    end

    def _remove_has_one_link(association_type)
      association = self.class._associations[association_type]

      send("#{association.foreign_key}=", nil)
      @save_needed = true
    end

    def _replace_fields(field_data)
      field_data[:attributes].each do |attribute, value|
        begin
          send "#{attribute}=", value
          @save_needed = true
        rescue ArgumentError
          # :nocov: Will be thrown if an enum value isn't allowed for an enum. Currently not tested as enums are a rails 4.1 and higher feature
          raise JSONAPI::Exceptions::InvalidFieldValue.new(attribute, value)
          # :nocov:
        end
      end

      field_data[:has_one].each do |association_type, value|
        if value.nil?
          remove_has_one_link(association_type)
        else
          replace_has_one_link(association_type, value)
        end
      end if field_data[:has_one]

      field_data[:has_many].each do |association_type, values|
        replace_has_many_links(association_type, values)
{'fraction_non_alphanumeric': 0.0590341454894769, 'fraction_numerical': 0.0003493144703519343, 'mean_word_length': 2.2222847495779403, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4975650', 'n_tokens_mistral': 3268, 'n_tokens_neox': 3166, 'n_words': 706}