Make shuffle order persistent across loops (Ruby on Rails) - ruby-on-rails

I need to access the same random shuffling across two different loops on the same ERB page.
Context:
I have questions which have five choices of which one is TRUE for choices.is_correct.
My current ERB (below) successfully displays:
1) Grouped by usage
2) All available questions (shuffled)
3) And all five choices (also shuffled)
What I'd like to add is a separate loop (directly after in the same ERB), which gives:
1) Same grouping by usage
2) Same order of available questions (eg, same shuffle order)
3) The correct answer choice (eg, answer "B" = referencing the same shuffling)
Assigning the question or choice shuffling globally would not work, as I need it to be randomized on each page load.
Current ERB (currently shuffles each loop separately)
<%= #exam.name %>
<br />
<% alpha_numbers = ("A".."Z").to_a %>
<% #book_questions_by_usage.each do |usage, question| %>
<h4><%= usage if usage %></h4>
<% question.shuffle.each_with_index do |question, i| %>
<%= i+1 %>: <%= question.name %>
<ol type="A">
<% question.choices.shuffle.each_with_index do |choice, index| %>
<% choice.alpha_order = alpha_numbers[index] %>
<li><%= choice.name %></li>
<% end %><br />
</ol>
<% end %>
<% end %>
<% #book_questions_by_usage.sort.each do |usage, question| %>
<h4><%= usage if usage %></h4>
<% question.shuffle.each_with_index do |question, i| %>
<%= i+1 %>: <%= question.name %>
<ol type="A">
<% question.choices.select { |choice| choice.correct }.shuffle.each_with_index do |choice, index| %>
<% choice.alpha_order = alpha_numbers[index] %>
Correct Answer: <b><%= choice.alpha_order %>. <%= choice.name %></b>
<% end %><br />
</ol>
<% end %>
<% end %>
question.rb
class Question < ApplicationRecord
before_validation :assign_questionable
belongs_to :questionable, polymorphic: true
has_many :choices, :dependent => :destroy
accepts_nested_attributes_for :choices, allow_destroy: true
choice.rb
class Choice < ApplicationRecord
belongs_to :question
I've spent hours looking into possible solutions with no success so far. Any help is much appreciated!

How about you don't shuffle in the view, but in the controller. Save the shuffled array in an instance method and then use it in the view where you need it.

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.

Error with instance variable in Index view

So in my tutors_controller.rb this is my index action
def index
#tutor = Tutor.all
#tutor = #tutor.fees_search(params[:fees_search]) if params[:fees_search].present?
end
and in my index.html.erb this is the view
<div class='container'>
<%= form_tag(tutors_path, method: :get) do %>
<%= label_tag 'fees_search', 'Max Fees' %>
<%= select_tag 'fees_search', options_for_select((10..50).step(10)) %>
<%= submit_tag 'Filter' %>
<% end %>
<% #tutor.each do |tutor| %>
<% unless tutor.admin? %>
<div class='row' id='tutor-listing'>
<div class='col-xs-4'>
<%= image_tag(tutor.profile.avatar.url, :class => "img-rounded" ) if tutor.profile.avatar? %>
</div>
<div class='col-xs-8'>
<h3><%= link_to tutor.full_name, tutor_path(tutor) %></h3>
<% unless tutor.subjects.nil? %>
<% tutor.subjects.each do |subs| %>
<span class='badge'id='tutor-listing-badge'>
<%= link_to subs.name, subject_path(subs) %>
</span>
<% end %>
<% end %>
<% unless current_tutor %>
<%= button_to "Shortlist Tutor", add_to_cart_path(tutor.id), :method => :post %>
<% end %>
</div>
</div>
<% end %>
<% end %>
</div>
So i understand that when the index view first renders, #tutor would simply be Tutor.all so it renders each individual tutor perfectly.
After trying to filter it though, i start receiving errors. The exact error is NoMethodError in Tutors#indexand the highlighted line is <% unless tutor.admin? %>
profile.rb model
class Profile < ActiveRecord::Base
belongs_to :tutor
scope :fees_to, -> (fees_to) { where("fees_to <= ?", "#{fees_to}") }
end
tutor.rb model
class Tutor < ActiveRecord::Base
has_one :profile, dependent: :destroy
def self.fees_search(n)
#profile = Profile.fees_to(n)
if #profile.empty?
return Tutor.none
else
#profile.each do |y|
y.tutor
end
end
end
end
I get that now my #tutor instance variable has obviously changed. But how do i go about resolving this problem? Should i be rendering a partial instead? Obviously my index action in my controller could be "better" also but i'm quite confused now as to what i should be doing.
Would appreciate any advice! Thank you!
#profile.each do |y|
y.tutor
end
Seems to be a problem. All the other outcomes are a Tutor.something scope, whereas this will return the last tutor only. Change each to map to get an array of Tutors instead.

Create a Ruby feed of two un-related tables

I'm creating an app that has two sets of content that's unrelated.
The first is Questions and Answers (Q&As) where a user can ask a question and the community can answer.
The second is an RSS like feed where an article is posted and links to a 3rd party site.
I'd like to create a 'Feed' so when the user logs in, they see the latest of the Q&As and the latest news all mixed together. I've got it working now where they aren't mixed together.
So two questions, how do i combine the two data sets? And what is the code to make it viewed given it's different content in each table.
Here is my code:
app>models>feed.rb
class Feed < ActiveRecord::Base
validates :name, :url, :description, :source, presence: true
end
app>models>question.rb
class Feed < ActiveRecord::Base
belongs_to :user
has_many :answers, dependent: :destroy, foreign_key: "id"
end
app>controllers>feeds_controller.rb
def index
#feeds = Feed.where("created_at >= ?", Date.today)
#questions = Question.where("created_at >= ?", Date.today)
end
app>views>feeds>index.html.erb
<% #feeds.each do |feed| %>
<h3><%= feed.name %></h3>
<p><%= feed.source %></p>
<p> <%= feed.created_at.strftime("%b %d, %Y") %> </p>
<%= link_to image_tag(feed.image.url(:medium)), feed.url %><br>
<%= truncate(feed.description, length: 50) %><br>
<% end %>
<% #questions.each do |question| %>
<tr>
<td><h1><%= link_to question.question , question_path(question) %></h1></td>
<td><p>Posted by: <%= question.user.name %></p></td>
<td> <p><%= question.created_at.strftime("%b %d, %Y") %> </p></td>
<td><p>Number of answers: <%= question.answers.count %> </p></td>
</tr>
<% end %>
I don't think join works given they have different data. Any suggestions on how to combine feed and questions into one stream with the most recent at the top? (like a facebook feed).
thanks for your help!
I'd suggest this... which is doing array sorting, but I think you don't have a choice with two unrelated object types.
#combined = (#feeds.to_a + #questions.to_a).sort{|a,b| b.created_at <=> a.created_at}
Then in the view...
<% #combined.each do |combined| %>
<%= render combined %>
<% end %>
The beauty of the render is that it will render a partial appropriate to the type of object. If combined s a Feed object the partial used will be feeds/_feed but if it's a Question object it will use the partial questions/_question
I was able to get this working with a few small changes. Rather than rendering the partial I did the following
<% #combined.each do |combined| %>
<% if combined.is_a?(Feed) %>
<h3><%= combined.name %></h3>
<p><%= combined.source %></p
...
<% else %>
<% end %>
Thank you for your help!

Rails: how to count number of users?

here is my models.
User
unit_id
Unit
block_id
Block
postalcode_id
Postalcode
neighbourhood_id
Neighbourhood
name
the relations is for all is top belongs to bottom
this is my current index.html.erb file, i wish to output the number of user in each neighbourhood.
<% provide(:title, 'Neighbourhoods') %>
<ul class="thumbnails">
<% #neighbourhoods.each do |neighbourhood| %>
<li class="span3">
<div class="thumbnail">
<div style="position:relative;">
<%= link_to "Join", '#', class: "btn-join" %>
<%= image_tag(neighbourhood.name+".jpg", alt: neighbourhood.name) %>
</div>
<h2 style="margin-bottom:0px"><%= neighbourhood.name.titleize %></h2>
<% neighbourhood.postalcodes.each do |postalcode| %>
<%= postalcode.blocks.map(&:block).join(", ") %>
<% end %>
<br>
<%= neighbourhood.streetname.titleize %>
</div>
</li>
<% end %>
</ul>
Thanks in advance.
Assuming a Neighborhood has_many Users:
<%= neighbourhood.users.size %>
Note that counting is a relatively slow option, so you can optionally cache the number of users for speed using counter_cache:
class User < ActiveRecord::Base
belongs_to :neighborhood, :counter_cache => true
end
Then in a migration:
add_column :neighborhoods, :users_count, :integer, :default => 0
Seems like an awfully deeply nested set of associations. You may want to take a second look at your models and see if you can 'trim them down' a little. Maybe something like just have a User and Unit model, then add block, postal code and neighbourhood to Unit where you could do Unit.block, and Unit.postal_code...ect.
That being said with your current configuration (assuming correct associations of has_many/belongs_to) you should be able to do something like:
Neighbourhood.postal_code.block.unit.users.count
Good luck!

group_by display belongs_to 'name' followed by has_many 'items'

I'm trying to display group name (product_group) followed by the items (product) in each group.
<% #products.group_by(&:product_group_id).each do |s|%>
<!--need to get group name here ->
<% s[1].each do |d|%>
<%= d.product_name%>
<br>
<%end%>
<%end%>
rails 2.3.8
First, I strongly recommend using two variables in your block. When you use one variable, group_by sets the variable to an array of the pair of values which should be set as two variables. It will be much more clear code than indexing the pair with [1] for the group.
One way is that the first part of the pair will be the id, so you can do a find.
<% #products.group_by(&:product_group_id).each do |product_group_id, products|%>
<!--need to get group name here -->
<%= ProductGroup.find(product_group_id).name %>
<% products.each do |product|%>
<%= product.product_name%>
<br>
<% end %>
<% end %>
Another way, is since you have an array of at least on product in the group, you can call the product_group association on the first element of the array.
<% #products.group_by(&:product_group_id).each do |product_group_id, products|%>
<!--need to get group name here -->
<%= products[0].product_group.name %>
<% products.each do |product|%>
<%= product.product_name%>
<br>
<% end %>
<% end %>
You can also delegate the name to the product group.
class Product
belongs_to :product_group
delegate :name, :to => :product_group, :prefix => true, :allow_nil => true
end
<%= products[0].product_group_name %>
Group by needs two variables in the block declaration. The first for the thing you are grouping by, and the second to hold the things in each of the groups.
<% #products.group_by(&:product_group_id).each do |group_id, group_products| %>
Group ID: <%= group_id %>
<br>
Products:
<% group_products.each do |product| %>
<%= product.product_name %>
<% end %>
<% end %>

Resources