Rails - Better way to query conditions on join table - ruby-on-rails

I am playing around with a bucket list and I have the following models:
BucketList > BucketListItem < Item
BucketListItem is the join table between the two.
When I'm displaying all of the items and allowing users to either add them to their own bucket list or mark them as complete, I need to know whether or not that user has the item on their bucket list, and also whether they have marked it as done or not. I've come up with a working solution but it feels like there should be a better way of doing it.
In the controller:
#user_items = current_user.bucket_list.items
#user_bucket_list_items = current_user.bucket_list.bucket_list_items
#completed_user_items = []
current_user.bucket_list.bucket_list_items.where(status: 1).each do |bucket_list_item|
#completed_user_items << bucket_list_item.item
end
In the partial that renders each of the items:
<% if #user_items.include?(item) %>
<p>Added</p>
<% else %>
<%= form_for(BucketListItem.new, remote: true) do |f| %>
<%= f.hidden_field :item_id, value: item.id %>
<%= f.submit "+ Add" %>
<% end %>
<% end %>
<% if #completed_user_items.include?(item) %>
<p>Done</p>
<% elsif #user_items.include?(item) %>
<%= form_for(#user_bucket_list_items.where(item_id: item.id).first, remote: true) do |f| %>
<%= f.hidden_field :status, value: "done" %>
<%= f.submit "Mark as Done" %>
<% end %>
<% else %>
<%= form_for(BucketListItem.new, remote: true) do |f| %>
<%= f.hidden_field :item_id, value: item.id %>
<%= f.hidden_field :status, value: "done" %>
<%= f.submit "Mark as Done" %>
<% end %>
<% end %>
It's particularly the second if statement that feels a little convoluted to me. Am I missing an obvious easier way to do this?
Thanks

Well view wise there is not much scope of optimization, But you can optimize your queries in controller
#In Controller
#bucket_list = current_user.bucket_list
#user_items = #bucket_list.items
# More efficient way of getting item which are not yet completed
#completed_user_items = current_user.items.where(bucket_list_items: { status: 1 })
#In view
<% if #user_items.include?(item) %>
<p>Added</p>
<% else %>
<%= form_for(BucketListItem.new, remote: true) do |f| %>
<%= f.hidden_field :item_id, value: item.id %>
<%= f.submit "+ Add" %>
<% end %>
<% end %>
<% if #completed_user_items.include?(item) %>
<p>Done</p>
#Here I have used #bucket_list
<% elsif #user_items.include?(item) %>
<%= form_for(item.bucket_list_items.where(bucket_list: #bucket_list).first, remote: true) do |f| %>
<%= f.hidden_field :status, value: "done" %>
<%= f.submit "Mark as Done" %>
<% end %>
<% else %>
<%= form_for(BucketListItem.new, remote: true) do |f| %>
<%= f.hidden_field :item_id, value: item.id %>
<%= f.hidden_field :status, value: "done" %>
<%= f.submit "Mark as Done" %>
<% end %>
<% end %>

Related

Save Question(s) form with one submit button

In my rails app I have a questions view that renders all the questions in a poll. I want to make sure that all the questions rendered can be submitted using 1 submit button.
If that's not possible, how can I render one question after another until there are no more quesitons?
The front-end code goes:
poll_question.html.erb:
<% #poll.questions.each do |qst| %>
<%= render "questions" , qst: qst%>
<p style="color: red"><%= notice %></p>
<hr>
<% end %>
Render partial _questions.html.erb:
<%= form_with model: qst.question_results.build, url: question_question_results_path(qst) do |f| %>
<% qst_type = qst.poll.voting_type %>
<% option_length = qst.options.count %>
<%= f.hidden_field :question_id, value: qst.id %>
<%= f.hidden_field :option_id, value: qst.options.first.id %>
<h3> <%= qst.title %></h3>
<h6> <%= qst.description %> </h6>
<ul>
<% qst.options.each do |option| %>
<%= f.fields_for :question_result_ranks, f.object.question_result_ranks.build do |rank_f| %>
<%= rank_f.hidden_field :option_id, value: option.id %>
<%= option.title %>
<%= rank_f.select :rank, options_for_select((1..option_length).step(1)) %><br>
<% end %>
<% end %>
</ul>
<div>
<%= f.submit "Save Answer" %>
</div>
<% end %>
Here is my routes.rb
resources :users do
resources :polls
# Questions
resources :questions, shallow: true do
resources :options
resources :question_results
patch "/create_question_results", to: "questions#create_question_results", as: "create_question_results"
end
end
Edit: These views are not rendered under the Poll and Question Controller, They are rendered under the session module which has no relationships in the model.
If you want to submit all the questions with one click, you should use a nested form.
Main template poll_question.html.erb:
<%= form_with model: #poll do |form| %>
<%= form.fields_for :questions do |f| %>
# This block renders a collection of partials.
<%= render 'question', f: %>
<% end %>
<%= form.submit 'Save' %>
<% end %>
Question form _quiestion.html.erb:
<% qst = f.object %>
<% qst_type = qst.poll.voting_type %>
<% option_length = qst.options.count %>
<%= f.hidden_field :question_id, value: qst.id %>
<%= f.hidden_field :option_id, value: qst.options.first.id %>
<h3> <%= qst.title %></h3>
<h6> <%= qst.description %> </h6>
<ul>
<% qst.options.each do |option| %>
<%= f.fields_for :question_result_ranks, f.object.question_result_ranks.build do |rank_f| %>
<%= rank_f.hidden_field :option_id, value: option.id %>
<%= option.title %>
<%= rank_f.select :rank, options_for_select((1..option_length).step(1)) %><br>
<% end %>
<% end %>
</ul>
And don't forget to add accepts_nested_attributes_for :questions in your poll model.

Rails :How to move code from view to partial file

In edit.html.erb
<% if material.look %>
<%= form_for material do |f|%>
<% if material.approval = "1" %>
<%= f.hidden_field :date,value: Date.today %>
<% end %>
<%= f.hidden_field :approval_amount, value: 0 %>
<%= f.hidden_field :approval_amount_percentage, value: 0 %>
<%= f.submit 'yes' %>
<% end %>
<%= form_for material do |f|%>
<%= f.hidden_field :approval_amount, value: 0 %>
<%= f.hidden_field :approval_amount_percentage, value: 0 %>
<%= f.submit 'no' %>
<% end %>
<% end %>
Code in two forms are duplicate, so I want to move them to partial file,
In _material_form.html.erb I code like this
<%= f.hidden_field :approval_amount, value: 0 %>
<%= f.hidden_field :approval_amount_percentage, value: 0 %>
In edit.html.erb, I modifies duplicate code
<% if material.look %>
<%= form_for material do |f|%>
<% if material.approval = "1" %>
<%= f.hidden_field :date,value: Date.today %>
<% end %>
<%= render 'material_form'%>
<%= f.submit 'yes' %>
<% end %>
<%= form_for material do |f|%>
<%= render 'material_form'%>
<%= f.submit 'no' %>
<% end %>
<% end %>
However, it shows an error
undefined local variable or method `f' for
What causes the error and how to fix it ? Thanks.
Your form, assigned to the variable f is out of scope in your partial. To use it, you'll need to pass f in as a local value to your partial:
<%= render 'material_form', f: f%>
The rails documentation on partials is quite helpful; you can also dig into the action renderer documentation if you want to go deeper.

Rails check_box form to create has_many relationship

I have a model called account which has_many :options. I want to create a form in which i can list all the options with a checkbox at the side, so the current account can select the options he/she wants inside a form so I can create the has_many relation.
This is what i have
def index
#account = current_account
#options = ['Op 1', 'Op 2', 'Op 3', 'Op 4']
end
and for the view:
<%= form_for(#account, url: options_path) do |f| %>
<% #options.each do |op| %>
<div class="checkbox">
<%= f.check_box(?????, {:multiple => true}, op, nil) %>
</div>
<% end %>
<%= f.submit class: 'btn btn-default' %>
<% end %>
This is obviously not working and I'm pretty sure this is not the right way to achieve what I want to do, so any help would be appreciated.
You could use fields_for:
<%= form_for(#account, url: options_path) do |f| %>
<%= fields_for :options do |options_form| %>
<% #options.each do |option| %>
<div class='checkbox'>
<%= options_form.label option do %>
<%= options_form.check_box option %> <%= option %>
<% end %>
</div>
<% end %>
<% end %>
<%= f.submit class: 'btn btn-default' %>
<% end %>
And in your params, you will get values like: params[:account][:options]['Op1'] with value '1' for true and '0' for false.

Having labels only appear once in field_for

What I currently have is:
<%= f.label :attachment_uploader, 'Current Attachments:' %>
<%= f.fields_for :data_files do |attachment| %>
<% if !attachment.object.new_record? %>
<%= attachment.label :attachment_uploader, 'Delete: ' + attachment.object.attachment_uploader_url.split("/").last %>
<%= attachment.check_box :_destroy %>
<% end %>
<% end %>
However, if I don't have any attachments the label is still there. For the sake of aesthetics I'd like it to be hidden unless I have attachments.
I was thinking something akin to:
<%= f.fields_for :data_files do |attachment, index| %>
<% if index == 0 %>
<%= attachment.label :attachment_uploader, 'Current Attachments:' %>
<% end %>
#rest of code
<% end %>
But that doesn't work!
I've read about the f.options[:index] in another post, but I couldn't figure it out.
Add an unless empty? condition before fields_for. #object will be the object for which you created the form
<%= f.label :attachment_uploader, 'Current Attachments:' %>
<% unless #object.data_files.empty? %>
<%= f.fields_for :data_files do |attachment| %>
<% if !attachment.object.new_record? %>
<%= attachment.label :attachment_uploader, 'Delete: ' + attachment.object.attachment_uploader_url.split("/").last %>
<%= attachment.check_box :_destroy %>
<% end %>
<% end %>
<% end %>

Using form_for with Awesome Nested Set

I have a Comment model with the acts_as_nested_set enabled, but when I try to do something like this (for nested comments), i receive the error "comment_comments_path not found", presumably because the default pathing doesn't work with Awesome Nested Set. How do I get around this?
<%= form_for([#comment, #comment.children.build]) do |f| %>
<%= f.text_area :content, :placeholder=>'What do you think?'%>
<%= f.submit 'Submit Reply'%>
<% end %>
I also tried this:
<%= form_for(#comment) do |f| %>
<% #comment.children.each do |sub| %>
<%= f.fields_for :children, sub do |child| %>
<%= child.text_area :content, :placeholder=>'What do you think?'%>
<%= f.submit 'Submit Reply'%>
<% end %>
<% end %>
<% end %>
but it didn't generate a textbox for me to type in.
You're very close, yeah you have to build it first then have fields for, so this:
<% #comment.children.build %>
<%= form_for([#comment]) do |f| %>
<%= f.fields_for :children do |child| %>
<%= child.text_area :content, :placeholder=>'What do you think?'%>
<% end %>
<%= f.submit 'Submit Reply'%>
<% end %>
<% end %>
This will have a form for all existing children + the new one. If you want only a form for a new child then you'll want this instead:
<%= form_for([#comment]) do |f| %>
<%= f.fields_for #comment.children.build, :children do |child| %>
<%= child.text_area :content, :placeholder=>'What do you think?'%>
<% end %>
<%= f.submit 'Submit Reply'%>
<% end %>
<% end %>

Resources