<commit_before>require 'representable/definition'

# Representable can be used in two ways.
#
# == On class level
#
# To try out Representable you might include the format module into the represented class directly and then
# define the properties.
#
#   class Hero < ActiveRecord::Base
#     include Representable::JSON
#     property :name
#
# This will give you to_/from_json for each instance. However, this approach limits your class to one representation.
#
# == On module level
#
# Modules give you much more flexibility since you can mix them into objects at runtime, roughly following the DCI
# pattern.
#
#   module HeroRepresenter
#     include Representable::JSON
#     property :name
#   end
#
#   hero.extend(HeroRepresenter).to_json
module Representable
  attr_writer :representable_attrs
  
  def self.included(base)
    base.class_eval do
      extend ClassMethods
      extend ClassMethods::Declarations
      extend ClassMethods::Accessors
      
      def self.included(base)
        base.representable_attrs.push(*representable_attrs) # "inherit".
      end
      
      # Copies the representable_attrs to the extended object.
      def self.extended(object)
        object.representable_attrs=(representable_attrs)
      end
    end
  end
  
  # Reads values from +doc+ and sets properties accordingly.
  def update_properties_from(doc, options, format)
    representable_bindings_for(format).each do |bin|
      next if skip_property?(bin, options)
      
      value = bin.read(doc) || bin.definition.default
      send(bin.definition.setter, value)
    end
    self
  end
  
private
  # Compiles the document going through all properties.
  def create_representation_with(doc, options, format)
    representable_bindings_for(format).each do |bin|
      next if skip_property?(bin, options)
      
      value = send(bin.definition.getter) || bin.definition.default # DISCUSS: eventually move back to Ref.
      bin.write(doc, value) if value
    end
    doc
  end
  
  # Checks and returns if the property should be included.
  def skip_property?(binding, options)
    return unless props = options[:except] || options[:include]
    res = props.include?(binding.definition.name.to_sym)
    options[:include] ? !res : res
  end
  
  def representable_attrs
    @representable_attrs ||= self.class.representable_attrs # DISCUSS: copy, or better not?
  end
  
  def representable_bindings_for(format)
    representable_attrs.map {|attr| format.binding_for_definition(attr) }
  end
  
  # Returns the wrapper for the representation. Mostly used in XML.
  def representation_wrap
    representable_attrs.wrap_for(self.class.name)
  end
  
  
  module ClassMethods # :nodoc:
    # Create and yield object and options. Called in .from_json and friends. 
    def create_represented(document, *args)
      new.tap do |represented|
        yield represented, *args if block_given?
      end
    end
    
    module Declarations
      def definition_class
        Definition
      end
      <commit_msg>moved code from #create_representation_from to #compile_fragment (and from #update_properties_from to #uncompile_fragment) to make it easier overriding internals.<commit_after>require 'representable/definition'

# Representable can be used in two ways.
#
# == On class level
#
# To try out Representable you might include the format module into the represented class directly and then
# define the properties.
#
#   class Hero < ActiveRecord::Base
#     include Representable::JSON
#     property :name
#
# This will give you to_/from_json for each instance. However, this approach limits your class to one representation.
#
# == On module level
#
# Modules give you much more flexibility since you can mix them into objects at runtime, roughly following the DCI
# pattern.
#
#   module HeroRepresenter
#     include Representable::JSON
#     property :name
#   end
#
#   hero.extend(HeroRepresenter).to_json
module Representable
  attr_writer :representable_attrs
  
  def self.included(base)
    base.class_eval do
      extend ClassMethods
      extend ClassMethods::Declarations
      extend ClassMethods::Accessors
      
      def self.included(base)
        base.representable_attrs.push(*representable_attrs) # "inherit".
      end
      
      # Copies the representable_attrs to the extended object.
      def self.extended(object)
        object.representable_attrs=(representable_attrs)
      end
    end
  end
  
  # Reads values from +doc+ and sets properties accordingly.
  def update_properties_from(doc, options, format)
    representable_bindings_for(format).each do |bin|
      next if skip_property?(bin, options)
      
      uncompile_fragment(bin, doc)
    end
    self
  end
  
private
  # Compiles the document going through all properties.
  def create_representation_with(doc, options, format)
    representable_bindings_for(format).each do |bin|
      next if skip_property?(bin, options)
      
      compile_fragment(bin, doc)
    end
    doc
  end
  
  # Checks and returns if the property should be included.
  def skip_property?(binding, options)
    return unless props = options[:except] || options[:include]
    res = props.include?(binding.definition.name.to_sym)
    options[:include] ? !res : res
  end
  
  # Retrieve value and write fragment to the doc.
  def compile_fragment(bin, doc)
    value = send(bin.definition.getter) || bin.definition.default # DISCUSS: eventually move back to Ref.
    write_fragment_for(bin, value, doc)
  end
  
  # Parse value from doc and update the model property.
  def uncompile_fragment(bin, doc)
    value = read_fragment_for(bin, doc) || bin.definition.default
    send(bin.definition.setter, value)
  end
  
  def write_fragment_for(bin, value, doc) # DISCUSS: move to Binding?
    return unless value
    bin.write(doc, value)
  end
  
  def read_fragment_for(bin, doc) # DISCUSS: move to Binding?
    bin.read(doc)
  end
  
  def representable_attrs
    @representable_attrs ||= self.class.representable_attrs # DISCUSS: copy, or better not?
  end
  
  def representable_bindings_for(format)
    representable_attrs.map {|attr| format.binding_for_definition(attr) }
  end
  
  # Returns the wrapper for the representation. Mostly used in XML.
  def representation_wrap
    representable_attrs.wrap_for(self.class.name)
  end
  
  
  module ClassMethods # :nodoc:
    # Create and yield object and options. Called in .from_json and friends. 
    def create_represented(document, *args)
      new.tap do |represented|
        yield represented, *args if block_given?
      end
    end
    
    module Declarations
      def definition_class
        Definition
      end
      
{'fraction_non_alphanumeric': 0.07237039256817501, 'fraction_numerical': 0.0, 'mean_word_length': 3.323186528497409, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2716972', 'n_tokens_mistral': 1942, 'n_tokens_neox': 1894, 'n_words': 602}