How does form_for generate paths in rails? - ruby

Can anyone explain me on how the paths are generated using form_for?. The below code generates a path article_comments_path.
<%= form_for [#article, #comment] do |f| %>
<% end %>
I mean how does it exactly generate article_comments_path and not articles_comment_path

It is using polymorphic_path method to determine the path. It is basicaly a wrpaper around polymorphic_url: http://apidock.com/rails/v4.0.2/ActionDispatch/Routing/PolymorphicRoutes/polymorphic_url
Update:
polymorphic_path is using internally method called build_named_route_call. When it gets an array, it pull the last record out of the array (with pop) and then iterates over the remaining array, changing all the objects to
if it is a model it takes it class and calls `model_name.singular_route_key'
if it is a symbol or string, leaves it as it is
Then we are left with the last element. It can be singular or plural, this is resolved in polymorphic_url method with this case statement:
inflection = if options[:action] && options[:action].to_s == "new"
args.pop
:singular
elsif (record.respond_to?(:persisted?) && !record.persisted?)
args.pop
:plural
elsif record.is_a?(Class)
args.pop
:plural
else
:singular
end
where record is a first element of the array (if array is passed). inlection var is then passed to build_named_route_call so it can build correct helper name. As you can see this form will generate different paths depending on whether the first element is already persisted or not.

Related

Can I use sort and uniq on an array of chef node attributes?

We have a template that generates a bash script, that includes a list of patches to check for based on node attributes - specifically the role assigned to the node.
patchlist=( <%= node['oracledb']['install']['oneoff']['db_oo_apply'] rescue nil %> <%= node['oracledb']['install']['oneoff']['db_psu_num'] rescue nil %> )
I've snipped the actual list a bit, but you get the point.
The issue is that there are potentially duplicate entries between these attributes, so we want to sort them and get a unique list.
Also, complicating matters, not all nodes in every environment have those attributes - which is why we have the rescue nil sprinkled in there.
I can build the array as
patchlist=( <%= ( node['oracledb']['install']['oneoff']['db_oo_apply'] + ' ' + node['oracledb']['install']['oneoff']['db_psu_num'] rescue nil).split(' ').uniq.sort.join(' ') %> )
which works, if all the attributes have values.
But what seems to happen is that if any single attribute in the list is empty, the rescue nil kicks in and the entire array comes back as nil.
I think that in the first variant, the scope of the rescue nil is limited to the specific attribute, but in the second it applies to the entire string.
So, can I somehow build this array, have it sorted and unique, and still control for cases where individual elements in the array might be null?
just use pure ruby for that, for instance:
irb(main):001:0> ([nil] + [2,1]).compact.uniq.sort
=> [1, 2]
in short, concatenate arrays into a new array, compact it to get a copy of the concatenated array with all nil elements removed, then apply uniq and sort.
One way to achieve this is to create an actual array of node attributes. In your current implementation, it is not an array, just attributes separated by space.
This is a little bit of logic overload on the template, but it should work.
Example template:
<% p_list = %W( #{node['oracledb']['install']['oneoff']['db_oo_apply']} #{node['oracledb']['install']['oneoff']['db_psu_num']} )
<% p_list_fin = p_list.uniq.sort %>
patchlist=( <% p_list_fin.each do |p| %><% next if p.nil? %><%= p %> <% end %>)
Of course the creation of array p_list of node attributes can be done from within recipe and passed in variables of the template resource as well.

Mutating list of instantiated objects in Rails

I'm attempting to clean up my view by moving Rails' sanitizer method to a helper, but it's not producing the desired result. So below is what my index action looks like. I know it's ugly and not very OOP, but I simplified it down so I could follow what was happening when debugging.
I'm attempting to loop through all the sources' attributes, running the sanitizer on any attribute that is a non-empty string, replacing original strings with the sanitized strings (transform_values!), and writing over the original #sources (map!).
I tried storing them in different variables than #sources and using .each instead of .map! but the sanitized values don't make it through.
def index
#sources = Source.all
#sources.map! { |source|
source.attributes.transform_values! { |attr|
attr.blank? || !attr.is_a?(String) ? attr
: ActionController::Base.helpers.sanitize(attr) } }
end
However, after examining my list of sources in the view, it's removing the source instances and instead returning a nondescript array of hashes. I can loop through these, but I can't call specific attributes like source.author which is not great.
Here's some images for reference. The first one is what it should look like and second is what I'm currently getting
Unsanitized sources
Sanitized sources
map! replaces each item in the array with the result of the block. This is not what you intend to do, because you just want to mutate the items, not replace them with something else. Use a plain each instead of map! would do the trick.
On another side, sanitization is actually a responsibility of the view (that’s why it’s defined in a helper). If you need to sanitize often with the same argument, define your own helper:
class ApplicationHelper
def sany(str)
sanitize(str, %w[...])
end
end
<%= sany(source.some_attr) %>
You could also set the default sanitization options following the documentation:
# In config/application.rb
config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
config.action_view.sanitized_allowed_attributes = ['href', 'title']

How to find a node given the exact HTML tag as a string (using Nokogiri)?

Question
I need to search a given web page for a particular node when given the exact HTML as a string. For instance, if given:
url = "https://www.wikipedia.org/"
node_to_find = "<title>Wikipedia</title>"
I want to "select" the node on the page (and eventually return its children and sibling nodes). I'm having trouble with the Nokogiri docs, and how to exactly go about this. It seems as though, most of the time, people want to use Xpath syntax or the #css method to find nodes that satisfy a set of conditions. I want to use the HTML syntax and just find the exact match within a webpage.
Possible start of a solution?
If I create two Nokogiri::HTML::DocumentFragment objects, they look similar but do not match due to the memory id being different. I think this might be a precursor to solving it?
irb(main):018:0> n = Nokogiri::HTML::DocumentFragment.parse(<title>Wikipedia</title>").child
=> #<Nokogiri::XML::Element:0x47e7e4 name="title" children=[ <Nokogiri::XML::Text:0x47e08c "Wikipedia">]>
irb(main):019:0> n.class
=> Nokogiri::XML::Element
Then I create a second one using the exact same arguments. Compare them - it returns false:
irb(main):020:0> x = Nokogiri::HTML::DocumentFragment.parse("<title>Wikipedia</title>").child
=> #<Nokogiri::XML::Element:0x472958 name="title" children=[#<Nokogiri::XML::Text:0x4724a8 "Wikipedia">]>
irb(main):021:0> n == x
=> false
So I'm thinking that if I can somehow create a method that can find matches like this, then I can perform operations of that node. In particular - I want to find the descendents (children and next sibling).
EDIT: I should mention that I have a method in my code that creates a Nokogiri::HTML::Document object from a given URL. So - that will be available to compare with.
class Page
attr_accessor :url, :node, :doc, :root
def initialize(params = {})
#url = params.fetch(:url, "").to_s
#node = params.fetch(:node, "").to_s
#doc = parse_html(#url)
end
def parse_html(url)
Nokogiri::HTML(open(url).read)
end
end
As suggested by commenter #August, you could use Node#traverse to see if the string representation of any node matches the string form of your target node.
def find_node(html_document, html_fragment)
matching_node = nil
html_document.traverse do |node|
matching_node = node if node.to_s == html_fragment.to_s
end
matching_node
end
Of course, this approach is fraught with problems that boil down to the canonical representation of the data (do you care about attribute ordering? specific syntax items like quotation marks? whitespace?).
[Edit] Here's a prototype of converting an arbitrary HTML element to an XPath expression. It needs some work but the basic idea (match any element with the node name, specific attributes, and possibly text child) should be a good starting place.
def html_to_xpath(html_string)
node = Nokogiri::HTML::fragment(html_string).children.first
has_more_than_one_child = (node.children.size > 1)
has_non_text_child = node.children.any? { |x| x.type != Nokogiri::XML::Node::TEXT_NODE }
if has_more_than_one_child || has_non_text_child
raise ArgumentError.new('element may only have a single text child')
end
xpath = "//#{node.name}"
node.attributes.each do |_, attr|
xpath += "[#{attr.name}='#{attr.value}']" # TODO: escaping.
end
xpath += "[text()='#{node.children.first.to_s}']" unless node.children.empty?
xpath
end
html_to_xpath('<title>Wikipedia</title>') # => "//title[text()='Wikipedia']"
html_to_xpath('<div id="foo">Foo</div>') # => "//div[id='foo'][text()='Foo']"
html_to_xpath('<div><br/></div>') # => ArgumentError: element may only have a single text child
It seems possible that you could build an XPath from any HTML fragment (e.g. not restricted to those with only a single text child, per my prototype above) but I'll leave that as an exercise for the reader ;-)

Ruby: showing one subarray per page

I am using Ruby (+ Sinatra) to work on one of my web projects.
I have an array with quite a bit of content and only want to display 10 of the array elements per page.
So what I did so far is:
creating an array with all of the content
ary = ["ex1", "ex2", "ex3", … ,"ex60" ] // all elements
splitting the array into subarrays
ary.each_slice(10).to_a // subarrays with fewer content
Now, I need a way to split the subarrays into single arrays and give them a name,
for example:
#subAry1 # ex1, ex2, ex3, … ex10
#subAry2 # ex11, ex12, ex13, … ex20
#subAry3 # ex21, ex22, ex23, … ex30
I stuck on creating these arrays with a continuing number in the array name.
When I would have my arrays split, I would use this in my .erb file:
<% currentAry = #subAry1 %>
<% currentAry.each do |element| %><%= element %><% end %>
and this to change the content, if the "next" button gets pressed:
currentAry = #subAry + '1'
Can somebody help me, or is it even an effective way, to split/display array elments on my page?
How about an array of arrays? Here's something to help you get started.
all = ary.each_slice(10).to_a
current_page = 0
In the ERB file:
<% all[current_page].each do |element| %><%= element %><% end %>
and after the "next" button gets pressed
current_page += 1

Ruby each loop x to variable substitution

<% #parts.each do|x| %>
<% #bbb = Relation.where(part: "#{x}") %>
<%= **#"#{x}"** = #bbb.collect {|x| x.car} %>
<% end %>
I'm trying to set the variable in line 3 to the x part value #"#{x}". I can't find the right syntax. I know about send(x) and x.to_sym. But I need to know how to set x in the each loop to an #variable with #. Thanks!
So you're looking for instance_variable_set
instance_variable_set "##{x}", #bbb.collect { |x| x.car }
But this is probably not the best way to handle this. Without seeing the code that uses it, its hard to really say, but maybe consider putting the results into a hash: result[x] = #bbb.collect { |x| x.car }
Also note that "#{x}" is the same as x.to_s
Also, it's best to avoid querying models directly in your views (assuming you're doing Rails here, since you appear to be using ActiveRecord), because it mixes presentation with code (you can't take them as separate pieces. It has a tendency to get really ugly), it couples your view to the one use case you initially had (can't present different data in the same view even though it should be presented the same). Consider moving this code into your controller (or even some other object whose responsibility is to manage the querying of this data, leaving your controller responsibilities at just dealing with the request.

Resources