Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive/Tree like strong parameters?

Is there a way of specifying arbitrarily deep strong parameters for tree structures in Rails 4? For example, how would I specify something as follows:

{
  "node": {
    "name": "parent",
    "nodes": [
      { "name": "child1", "nodes": []},
      { "name": "child2", "nodes": [
        {"name": "grandchild", "nodes": []}
      ]}
    ]
  }
}

To allow each node to have a name attribute, and a nodes attribute?

like image 884
moger777 Avatar asked Jul 15 '13 14:07

moger777


2 Answers

There may be a cleaner solution for solving this but this is my current work around. The general idea is to count how deep my nesting goes and then auto generate the correct nested hash based on that number. So to follow your example:

def count_levels(node_params)
   if !node_params[:nodes].nil?
    max = 0
    node_params[:node_parameters].each do |child_params|
      count = count_levels(child_params[1])
      max = count if count > max
    end
    return max + 1
  else
    return 0
  end
end

def node_params
  node_attributes_base = [:id, :name]
  nodes = []
  (1..count_levels(params[:node])).each do |val|
    nodes = node_attributes_base + [node_attributes: nodes]
  end
  params.require(:node).permit(:id, :name, node_attributes: nodes)
end

(The above example can be cleaned up more since it's based on my code where the top level did not have the same parameters. I left it as I had it since it worked on my system.)

like image 165
Rose Avatar answered Oct 23 '22 06:10

Rose


You can solve by depending on the fact that number of allowed level can be more than the levels you actually need, so you can count the occurrence of the recursive key nodes key in your hash and use this count as number of levels.

Note that this count will be more than the levels you actually need, but it's simpler than recursively count number of levels in the hash

So in your controller you can the following:

# your_controller.rb
# include RecursiveParametersBuilder
recursive_nodes_attr = build_recursive_params(
  recursive_key: 'nodes',
  parameters: params,
  permitted_attributes: [:name]
)
params.require(:model_name).permit(:name, nodes: recursive_nodes_attr)

And have the actual strong parameters building code can be like the following

# helper module 
module RecursiveParametersBuilder
  # recursive_path = [:post]
  # recursive_key = :comment_attributes
  # recursive_node_permitted_params = [:id, :_destroy, :parameterized_type, :parameterized_id, :name, :value, :is_encrypted, :base_param_id, :parent_param_id]
  #
  def build_recursive_params(recursive_key:, parameters:, permitted_attributes:)
    template = { recursive_key => permitted_attributes }

    nested_permit_list = template.deep_dup
    current_node = nested_permit_list[recursive_key]

    nested_count = parameters.to_s.scan(/#{recursive_key}/).count
    (1..nested_count).each do |i|
      new_element = template.deep_dup
      current_node << new_element
      current_node = new_element[recursive_key]
    end
    nested_permit_list
  end
end
like image 41
Moustafa Samir Avatar answered Oct 23 '22 08:10

Moustafa Samir