module ContentfulModel::Associations::HasManyNested::ClassMethods
Class method
Public Instance Methods
has_many_nested
allows you to set up a tree relationship it calls has_many and belongs_to_many on the class, and sets up some methods to find a deeply-nested instance's parents
To set this up in contentful, add a multi-entry field validated to the same model as the parent, and give it a name. For example, Page might have a field called childPages:
has_many_nested
:child_pages, root: -> { Page.find(“some_id”) }
would setup up an instance attribute called parent_pages which lists all the direct parents of this page. It would also create methods to find a page based on an array of its ancestors, and generate an array of ancestors. Note that this builds an array of the ancestors which called the object; because 'many' associations in Contentful
are actually 'belongs_to_many' from the child end, we might have several ancestors to a page. You will need to write your own recursion for this, because it's probably an implementation-specific problem. rubocop:disable Style/PredicateName
# File lib/contentful_model/associations/has_many_nested.rb, line 27 def has_many_nested(association_name, options = {}) has_many association_name, class_name: to_s, inverse_of: :"parent_#{has_many_nested_name}" belongs_to_many :"parent_#{has_many_nested_name.pluralize}", class_name: to_s root_method = options[:root] if options[:root].is_a?(Proc) # If there's a root method defined, set up a class method called root_[class name]. In our example this would be # Page.root_page. # @return [Object] the root entity returned from the proc defined in has_many_nested if defined?(root_method) && root_method.is_a?(Proc) # @return [Object] the root entity define_method :"root_#{has_many_nested_name}" do root_method.call end end # A utility method which returns the parent object; saves passing around interpolated strings define_method :parent do parents = send(:"parent_#{self.class.has_many_nested_name.pluralize}") parents.first unless parents.nil? end # Determine if the object has any parents. If it doesn't, it's considered a root. # This only works if the objects are called through their parents' 'child_[whatever]' method define_method :root? do parent.nil? end # Iterate over parents until you reach the root. # @param [Proc] a block to call on each ancestor # @return [Enumerable] which you can iterate over define_method :find_ancestors do |&block| return enum_for(:find_ancestors) unless block if parent.nil? # this *is* the parent return self end block[parent] parent.find_ancestors { |a| block[a] } if parent && !parent.root? end # A utility method to return the results of `find_ancestors` as an array # @return [Array] of ancestors in reverse order (root last) define_method :ancestors do find_ancestors.to_a end # If this entry is the root, return self. # Otherwise, return the last member of the ancestors, which is the root # @return the root instance of this object define_method :root do return self if root? find_ancestors.to_a.last end # @return [Boolean] whether or not this instance has children define_method :children? do !send(association_name).empty? end # @return [Array] a collection of child objects, based on the association name define_method :children do send(association_name) end # @return [Hash] a hash of nested child objects define_method :nested_children do children.each_with_object({}) do |e, a| children = e.children? ? e.nested_children : nil a[e] = children end end # Return a nested hash of children, returning the field specified # @param field [Symbol] the field you want to return, nested for each child # @return [Hash] of nested children, by that field define_method :nested_children_by do |field| children.each_with_object({}) do |e, a| children = e.children? ? e.nested_children_by(field) : nil a[e.send(field)] = children end end # Return a flattened hash of children by the specified field define_method :all_child_paths_by do |field, opts = {}| options = { prefix: nil }.merge!(opts) flatten_hash(nested_children_by(field)).keys.collect do |path| options[:prefix] ? path.unshift(options[:prefix]) : path end end # Search for a child by a certain field. This is called on the parent(s). # e.g. Page.root.find_child_path_by(:slug, "some-slug"). Accepts a prefix if you want to # prefix the children with the parent define_method :find_child_path_by do |field, value, opts = {}| all_child_paths_by(field, opts).select { |child| child.include?(value) } end # Private method to flatten a hash. Courtesy Cary Swoveland http://stackoverflow.com/a/23861946 define_method :flatten_hash do |h, f = [], g = {}| return g.update(f => h) unless h.is_a? Hash h.each { |k, r| flatten_hash(r, f + [k], g) } g end send(:private, :flatten_hash) end
# File lib/contentful_model/associations/has_many_nested.rb, line 135 def has_many_nested_name to_s.demodulize.underscore end