class ParameterBuilder def self.serial_parameters(only_string, object) only_array = split_only_string(only_string) get_only_hash(only_array, object) end def self.get_only_hash(only_array, object, seen_objects = []) return {} if object.nil? is_root = seen_objects.length == 0 only_hash = {only: [], include: [], methods: []} available_includes = object.available_includes attributes, methods = object.api_attributes.partition { |attr| object.has_attribute?(attr) } methods -= available_includes # Attributes and/or methods may be included in the final pass, but not includes seen_objects << object.class.name only_array.each do |item| match = item.match(/(\w+)\[(.+?)\]$/) item = (match || [])[1] || item item_sym = item.to_sym was_seen = was_inclusion_seen(item, object.class, seen_objects) if match && available_includes.include?(item_sym) && (!was_seen || is_root) item_object = object.send(item_sym) next if item_object.nil? item_object = item_object[0] if item_object.is_a?(ActiveRecord::Relation) item_array = split_only_string(match[2]) item_hash = get_only_hash(item_array, item_object, seen_objects.clone) only_hash[:include] << Hash[item_sym, item_hash] elsif available_includes.include?(item_sym) && (!was_seen || is_root) only_hash[:include] << item_sym elsif attributes.include?(item_sym) only_hash[:only] << item_sym elsif methods.include?(item_sym) only_hash[:methods] << item_sym only_hash[:only] << item_sym end end only_hash.delete(:include) if only_hash[:include].empty? only_hash.delete(:methods) if only_hash[:methods].empty? only_hash end def self.includes_parameters(only_string, model_name) return [] if only_string.blank? only_array = split_only_string(only_string) get_includes_array(only_array, model_name) end def self.get_includes_array(only_array, model_name, seen_objects = []) is_root = seen_objects.length == 0 include_array = [] model = Kernel.const_get(model_name) available_includes = model.available_includes # Attributes and/or methods may be included in the final pass, but not includes seen_objects << model_name only_array.each do |item| match = item.match(/(\w+)\[(.+?)\]$/) item = (match || [])[1] || item item_sym = item.to_sym was_seen = was_inclusion_seen(item, model, seen_objects) if match && available_includes.include?(item_sym) && (!was_seen || is_root) item_array = split_only_string(match[2]) model.associated_models(item).each do |m| item_array = get_includes_array(item_array, m, seen_objects.clone) include_array << (item_array.empty? ? item_sym : Hash[item_sym, item_array]) end elsif available_includes.include?(item_sym) && (!was_seen || is_root) include_array << item_sym end end include_array end def self.was_inclusion_seen(inclusion, class_object, seen_objects) if class_object.reflections[inclusion] inclusion_class = class_object.reflections[inclusion].class_name max_seen = (class_object.multiple_includes.include?(inclusion.to_sym) ? 1 : 0) seen_objects.count(inclusion_class) > max_seen else false end end def self.split_only_string(only_string) only_array = [] offset = 0 position = 0 level = 0 loop do str = only_string[Range.new(position, -1)] match = str.match(/[,\[\]]/) break unless match start_pos, end_pos = match.offset(0) if match[0] == "," && level.zero? only_array << only_string[Range.new(offset, position + start_pos - 1)] offset = position + end_pos elsif match[0] == "[" level += 1 elsif match[0] == "]" level -= 1 end position += end_pos end only_array << only_string[Range.new(offset, -1)] end end