Rails: Retrieve value from hash in array - ruby-on-rails

There's a good number of related questions but their answers haven't helped me. I have a method fetch_all_sections which populates an array with this line:
all_sections << {:id => section.id, :sortlabel => section.sortlabel, :title => section.title, :depth => depth}
In a loop in a view, I would like to easily access the values by their key, like this:
<% fetch_all_sections(#standard).each do |section| %>
<%= section.id %>
<% end %>
This says no method id on section. section[:id] and #{section['id']} have similarly themed errors. I used a hash for ease of retrieval - should I use a different structure?
I'm hoping I don't need .map like section.map { |id| id[:id] } for every value.
EDIT: Here's the context. It's a little loopy (pun intended) but it does what's intended.
# Calls itself for each section recursively to fetch all possible children
def fetch_all_sections(standard, section = nil, depth = 0)
all_sections = []
if section.nil?
rootsections = standard.sections.sorted
if ! rootsections.nil?
rootsections.each_with_index do |section, i|
depth = section.sortlabel.split('.').length - 1
all_sections.push(fetch_all_sections(standard, section, depth))
end
end
else
all_sections << {:id => section.id, :sortlabel => section.sortlabel, :title => section.title, :depth => depth}
section.children.sorted.each do |section|
all_sections | fetch_all_sections(standard, section)
end
end
return all_sections
end

Try with the following:
<% fetch_all_sections(#standard).each do |section| %>
<%= section['id'] %>
<% end %>
If not working, try debugging using these methods:
<% fetch_all_sections(#standard).each do |section| %>
<%= section.inspect %>
<%= section.class %>
<% end %>
As the Question author said, this fixed:
all_sections << fetch_all_sections(standard, section, depth).first
And tell us the output of the inspect

Related

Rails, edit action does not populate the form correctly with many instances of the same model

I'm new to Rails and I'm doing my first project. Also, English is not my native language so bear with me, please.
The problem I'm having is that I have a form with multiple instances of the same model, the data is being created correctly but when I try to edit it the form is populated in the wrong way.
I'm making an app to check if everything goes according to the rules.
The items to be checked are in a nested association Chapters->Subchapters->Checks
Every time the checks are submitted a CheckRound is created and the information of every check is stored separately in CheckResults.
CheckRounds
has_many :check_results, inverse_of: :check_round, dependent: :destroy
accepts_nested_attributes_for :check_results, reject_if: proc { |att| att['observation'].blank? }
CheckResults
belongs_to :check_round, optional: true, inverse_of: :check_results
belongs_to :check
Chapters
has_many :subchapters
Subchapters
belongs_to: chapter
has_many: checks
Checks
belongs_to :subchapter
has_many :check_results
The form displays all the Chapters and the nested Subchapters and Checks.
Every Check displays its name and has a text_area as an input.
The user can fill none or many Checks.
<%= form_for(#check_round, :url => {:action => 'update', :client_id => #client.id, :project_id => #project.id}) do |f| %>
<% #chapters.each do |chapter| %>
<%= chapter.name %>
<% chapter.subchapters.each do |subchapter| %>
<%= subchapter.name %>
<% subchapter.checks.each do |check| %>
<%= f.fields_for :check_results do |result| %>
<%= check.name %>
<%= result.hidden_field(:check_id, :value => check.id) %>
<%= result.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s) %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
The controller is
def edit
#check_round = CheckRound.includes(:check_results).find(params[:id])
#chapters = Chapter.includes(subchapters: :checks).where("segment_id = ?", #project.segment_id).sorted
end
If for example, I submit that check.id = 3 has the observation = "bad" when I go to edit every check has "bad" in its observation regardless of its id.
I want to know how can I show in edit all the checks with a blank observation but the ones that were created.
Thanks in advance for your time!
Ok, From what i see 2 things that needs to fixed.
1st, your f.fields_for :check_results do |result|
needs an extra parameter to specify which check_results it exactly has to modify... somethings like this:
f.fields_for :check_results, #check_round.check_results.where(check_id: check.id) do |result|
in the exact same place so the check variable is specify the right way.
2de, you need to permit your nested parameters in your controller so they can be saved when u submit. Normally you should see a method called check_round_params in your check_round controller.
this one have to like this for everything to work:
def check_round_params
params.require(:check_round_params).permit(
/*your needed params*/,
check_results_attributes: [:id, :check_id, :observation, /*all your nested params*/]
)
end
In short, your update and your create actions work according to those permitted params, so you need define them there. check_results_attributes: is the way that rails understands those params are for nested models.
Here is some documentation you might find interesting:Nested attributes example
Here is the solution i've promised.
Sinds you have already defined that check results with blank observations had to be rejected and there will to much logic involved in your erb for its own sake, i would put it all in an helper method so your erb will be cleaner. Something like this:
#helpers/check_rounds_helper.rb
def edit_or_instantiate_nested_check_results(f, check_round, check, new_check_result)
if check.check_results
f.fields_for :check_results, check_round.check_results.where(check_id: check.id) do |result|
result.hidden_field(:check_id, :value => check.id)
result.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s)
end #end for the already present check results
# if u want to add a new check result event if the check is populated
f.fields_for :check_results, new_check_result do |new|
new.hidden_field(:check_id, :value => check.id)
new.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s)
end #end for the new check result
else #if there is no existing check result nest a form for a new one
f.fields_for :check_results, new_check_result do |new|
new.hidden_field(:check_id, :value => check.id)
new.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s)
end #end for the new check result
end #end if statement
end
Then in your view:
<%= form_for(#check_round, :url => {:action => 'update', :client_id => #client.id, :project_id => #project.id}) do |f| %>
<% #chapters.each do |chapter| %>
<%= chapter.name %>
<% chapter.subchapters.each do |subchapter| %>
<%= subchapter.name %>
<% subchapter.checks.each do |check| %>
<%= check.name %>
<% new_check_result = CheckResult.new(check_round_id: #check_round.id, check_id = check.id) %>
<%= edit_or_instantiate_nested_check_results(f, #check_round, check, new_check_result) %>
<% end %>
<% end %>
<% end %>
<% end %>
And that shoud be it ;). Let me know if it did the trick :D!
KR,
I believe it works like you want with this (code with some simplifications):
Check
class Check < ApplicationRecord
belongs_to :subchapter
has_many :check_results
def check_results_for_form check_round_id
results = check_results.where(check_round_id: check_round_id)
results.any? ? results : check_results.build
end
end
CheckRoundsController
def edit
#check_round = CheckRound.find(params[:id])
#chapters = Chapter.includes(subchapters: :checks).all
end
edit.html.erb
<%= form_for(#check_round, :url => {:action => 'update'}) do |f| %>
<ul>
<% #chapters.each do |chapter| %>
<li>
<%= chapter.name %>
chapter
<ul>
<% chapter.subchapters.each do |subchapter| %>
<li>
<%= subchapter.name %>
subchapter
<ul>
<% subchapter.checks.each do |check| %>
<li>
<%= check.name %>
check
<br>
<%= f.fields_for :check_results, check.check_results_for_form(#check_round.id) do |result| %>
<%= result.hidden_field(:check_id, :value => check.id) %>
<%= result.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s) %>
<% end %>
</li>
<% end %>
</ul>
</li>
<% end %>
</ul>
</li>
<% end %>
<ul>
<%= f.submit %>
<% end %>
Your problem is that you are repeating the display of the form fields for check_results. Look at line 7 of your view code:
<%= f.fields_for :check_results do |result| %>
This is displaying the fields for each check result on f.object (which is #check_round). However, this code gets repeated for each check in subchapter. That surrounding block gets repeated for each subchapter in chapter, and the block surrounding that gets repeated for each chapter in #chapters.
When the form is submitted, the params for check_results all have the same names, they are not distinguished by chapter, subchapter, or check. As a result, the only value that gets saved for observation is the last one submitted.
I think a solution for your case would be to only show the check_result form fields associated with the current check in the loop. One way to do that is to put a conditional in the loop starting on line 7 of your view code:
<%= f.fields_for :check_results do |result| %>
<% if result.object.check == check %>
<%= result.hidden_field(:check_id, :value => check.id) %>
<%= result.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s) %>
<% end %>
<% end %>
<% end %>
You could also just loop through the check_results independently of the loops for checks, subchapters, and chapters, but I'm assuming that you want to keep that order and context for the UI.

Rails delete_if using hashes to ignore current article (Middleman) revisited

I've got an easy one for you guys.
I want to have a featured content section where the current article link is hidden or removed
So already this works using Middleman Blog with delete_if:
<% blog(content).articles.delete_if{|item| item == current_article}.each do |article| %>
<%= article.href %>
<% end %>
However I'm using Middleman proxies so I don't have access to the current_article method...
I've got an YAML structure that holds the following mock data (amongst others) with the folder setup like: data > site > caseStudy > RANDOM-ID423536.yaml (Generated by a CMS)
Inside each yaml file you'll find stuff like:
:id: 2k1YccJrQsKE2siSO6o6ac
:title: Heyplace
My config.rb looks like this
data.site.caseStudy.each do | id, this |
proxy "/cases/#{this.slug}/index.html", "/cases/cases-template.html", :locals => { this: this }, :ignore => true
end
My template file holds the following
<%= partial(:'partials/footer', :locals => {:content => data.site.caseStudy, :title => 'See more projects'}) %>
My loop that gets content
<% content.each do |id, this| %>
<%= partial "partials/tile" %>
<% end %>
So I guess my question is, how can I use delete_if in this instance so that the current article isnt being displayed?
I have tried stuff like, but to no avail:
<% content.delete_if{|e| e.title == content.title}.each do |id, article| %>
<%= article.href %>
<% end %>
As well as:
<% content.reject{|key, value| key >= this.id }.sort_by{ |index| rand }.each do |id, this| %>
<%= partial "partials/tile" %>
<% end %>
(This deletes the data all together forever until you reload the page, strange!)
Any help is appreciated! Thank you!

rails delete_if using hashes to ignore current article (Middleman)

I've got an easy one for you guys.
I want to have a featured content section where the current article is EXCLUDED
So this works using Middleman Blog with delete_if:
<% blog(content).articles.delete_if{|item| item == current_article}.each do |article| %>
<%= article_content %>
<% end %>
However I'm using Middleman proxies so I don't have access to the current_article method...
I've got an YAML structure that holds the following mock data (amongst others) with the folder setup like: data > site > caseStudy > RANDOM-ID423536.yaml (Generated by a CMS)
Inside each yaml file you'll find stuff like:
:id: 2k1YccJrQsKE2siSO6o6ac
:title: Heyplace
My config.rb looks like this
data.site.caseStudy.each do | id, this |
proxy "/cases/#{this.slug}/index.html", "/cases/cases-template.html", :locals => { this: this }, :ignore => true
end
My template file holds the following
<%= partial(:'partials/footer', :locals => {:content => data.site.caseStudy, :title => 'See more projects'}) %>
My loop that gets content
<% content.each do |id, this| %>
<%= partial "partials/tile" %>
<% end %>
So I guess my question is, how can I use delete_if in this instance so that the current article isnt being displayed?
I have tried stuff like, but to no avail:
<% content.delete_if{|e| e.title == content.title}.each do |id, this| %>
<%= partial "partials/tile" %>
<% end %>
Any help is appreciated! Thank you!
Solved, ended up doing
<% content.reject{ | id, item| item == this }.each do |id, this| %>
<%= partial "partials/tile", :locals => { :this => this, :dir => "/#{this.content_type_id.downcase}/", :secondary => 'View' } %>
<% end %>
Courtesy of #David Litvak
I'm the maintainer of contentful_middleman. Looks like you could just send the this to the partial as local, then iterate through your content and make sure that you exclude the one that matches the id of this.

Count the number of records in group_by clause

I have the following categories grouped together:
#categories = Category.all.group_by { |c| c.name }
In my view I am displaying the category names like so:
<% #categories.each do |c, v| %>
<li><%= link_to c, blog_path(:name => c) %></li>
<% end %>
Which gives this for example:
Ruby
Ruby On Rails
CSS
What I want to achieve is next to each category name have the total number of posts with that category name, so:
Ruby(2)
Ruby On Rails(10)
So I have tried:
#categories = Category.joins(:posts).all.group_by { |c| c.name }
Which results in only the categories with a post object being displayed (previously all categories would display, regardless of whether they had a post object) and in my view I tried:
<% #categories.each do |c, v| %>
<li><%= link_to c, blog_path(:name => c) %><%= c.count %></li>
<% end %>
This is outputting nothing. I'd like to find how to approach this before I confuse the matter.
It's confusing to call the grouped categories #categories because that makes it sound like a collection of Category objects, when it's actually a Hash. Using descriptive names, including in your loop, makes your code much clearer.
Try this:
#category_groups = Category.includes(:posts).all.group_by { |c| c.name }
and the view
<% #category_groups.each do |name, categories| %>
<li><%= link_to name, blog_path(:name => name) %> (<%= categories.map{|category| category.posts.size}.sum %> posts)</li>
<% end %>
I'm not sure why you're using group_by. However, you don't need a join because you don't have any condition on posts. Other examples suggest eager loading posts, but that seems overkill to initialise all the post objects in memory to just get the count of them. You'd need to do your own benchmarks though. Consider a counter_cache like another answerer suggested.
#categories = Category.all
and in the view:
<% #categories.each do |c| %>
<li><%= link_to c.name, blog_path(:name => c.name) %> (<%= c.posts.count %> posts)</li>
<% end %>
Further explanation
group_by returns a hash with the key as the unique return value from the block, and the value are all items of the original array for which the block evaluates to that key. Taking your example of Category.all (the scope is converted to an array before group_by so that's how we'll represent it here):
cats = [
#<Category name: "foo" ... >,
#<Category name: "bar" ... >,
#<Category name: "baz" ... >
]
These three categories have unique names, so using .group_by { |c| c.name } does nothing but create a pointless hash with the keys as the name, and each value as an array with one Category object like:
{
"foo" => [#<Category name: "foo" ... >],
"bar" => [#<Category name: "bar" ... >],
"baz" => [#<Category name: "baz" ... >]
}
Here's an example of where you might use group_by to some effect:
languages = ["Ada", "C++", "CLU", "Eiffel", "Lua", "Lisp",
"Perl", "Python", "Smalltalk"]
languages_grouped_by_first_letter = languages.group_by { |s| s[0] }
=> {"A"=>["Ada"], "C"=>["C++", "CLU"], "E"=>["Eiffel"], "L"=>["Lua", "Lisp"], "P"=>["Perl", "Python"], "S"=>["Smalltalk"]}
Use Following code
#categories = Category.all.group_by { |c| c.name }
Write a Hepler to find post_count
def post_count(category_ids)
Post.where(:category_id => category_ids)
end
In View:
<% #categories.each do |c, v| %>
<li><%= link_to c, blog_path(:name => c) %>(<%= post_count(v.map(&:id)) %>)</li>
<% end %>
Hope this helps! :)
What you are looking for is called counter_cache.
In our Post model,set counter_cahe => true
class Post < ActiveRecord::Base
belongs_to category,:counter_cache => true
end
Add posts_count column to categories table and do like this
#category_groups = Category.find(:all)
<% #category_groups.each do |c| %>
<li><%= link_to name, blog_path(:name => c) %>(<%= posts_count)</li>
<% end %>
Look this Railscast For implementing this.

rails 3: How can I concatenate a number to a symbol?

I would like the #comment1 to change to #comment2 by using the i in the 1..5 loop. I have the following code that is pretty repetitive. I am hoping to dry it up.
Hi,I am using acts_as_commentable_with_threading. I am basically looping through all comments and checking to see if that comment has children. If so, print out the children while checking to see if those children have children. So I plan on going a few levels deep, hence the #comment1,2,3, etc...How can I DRY this? Recursion some how? If not, I could maybe go a few levels deep and end the comment indentation at #comment5 for example.
EDIT!
Thank you Samiron!
Here is the updated helper function...
def show_comments_with_children(comments)
comments.each do |comment|
yield comment
if comment.children.any?
concat <<-EOF.html_safe
<div class="span7 offset1-1 pcomment">
EOF
show_comments_with_children(comment.children) { |x| yield x } #Dont worry, this will not run another query :)
concat <<-EOF.html_safe
</div>
EOF
end
end
end
<div class="span7 offset1-1 pcomment">
<% #comment1 = comment.children%>
<% for comment in #comment1 %>
<%= render "comment_replies", :comment => comment %>
<div class="span7 offset1-1 pcomment">
<% #comment2 = comment.children%>
<% for comment in #comment2 %>
<%= render "comment_replies", :comment => comment %>
<div class="span7 offset1-1 pcomment">
<% #comment3 = comment.children%>
<% for comment in #comment3 %>
<%= render "comment_replies", :comment => comment %>
<% end %>
</div>
....
<%(1..5).each do |i| %>
<% #comment1 = comment.children%>
<% for comment in #comment1 %>
<%= render "comment_replies", :comment => comment %>
<% end %>
<% end %>
Probably you are looking for instance_variable_set.
# Following snippet is not TESTED. It is here to just demonstrate "instance_variable_set"
<%(1..5).each do |i| %>
<% instance_variable_set("#comment#{i}", comment.children) %>
<% for comment in instance_variable_get("#comment#{i}") %>
<%= render "comment_replies", :comment => comment %>
<% end %>
<% end %>
But definitely this is not a recommendable approach. You can share your controller code and what you want to achieve in your view. There must be some way to make it properly DRY. In your post you are always getting comment.children. is it really?
Actual Solution:
Your view code will be like this
#0th level is the top level
<% show_comments_with_children(#comments, 0) do |comment, level|%>
<!-- #Use level to differ the design for different depth-->
<%= render "comment_replies", :comment => comment %>
<%end%>
and add this helper function show_comments_with_children in your helper function. Which will be.
def show_comments_with_children(comments, level)
comments.each do |comment|
yield comment, level
if comment.children.any?
show_comments_with_children(comment.children, level+1) {|x, l| yield x, l} #Dont worry, this will not run another query :)
end
end
end
The manor in which you are defining this code is rather poor, and you should consider defining #comment as an array rather than as independent variables for each #comment1, #comment2, etc..
That said, what you are looking for is the instance_variable_get() method
<(1..5).each do |i| %>
<% instance_variable_set("#comment#{i}", comment.children) %>
<% for comment in instance_variable_get("#comment#{i}") %>
<%= render "comment_replies", :comment => comment %>
<% end %>
<% end %>
This is definitely something good to know but in this case I highly recommend converting you comment instance variables to an array!

Resources