In my application a user can say they have completed a piece of homework.
A field "completed_on" is populated with the date the homework was completed. If not completed the field is blank.
I would like to show a tick if the homework is completed or an x if the homework is not completed.
The column "completed_on" is located in a table called homework_students.
class Homework < ActiveRecord::Base
has_many :homework_students, :class_name => 'HomeworkStudent'
class HomeworkStudent < ActiveRecord::Base
belongs_to :homework, :class_name => 'Homework'
I have tried the following which does not work for me:
In my model:
def getCompletion
homework_students.where("completed_on is not null")
end
View:
<% if homework.getCompletion %><%= image_tag "fi-check.svg", class: "homework_student_complete" %><% else %><%= image_tag "fi-x.svg", class: "homework_student_complete" %><% end %>
I also tried this:
Model
def completed?
!homework.homework_student.completed_on.blank?
end
View
<% if homework.completed? %>
<%= image_tag "fi-check.svg", class: "homework_student_complete" %><% else %><%= image_tag "fi-x.svg", class: "homework_student_complete" %><% end %>
Appreciate any guidance.
UPDATE
This works:
def completed?
homework_students.where("completed_on is not null").length == 0
end
<% #homeworks.each do |homework| %>
...
<td height="1" class="text-center"><% if homework.completed? %>
<%= image_tag "fi-check.svg", class: "homework_student_complete" %>
<% else %>
<%= image_tag "fi-x.svg", class: "homework_student_complete" %>
<% end %></td>
...
<% end %>
The return type of your getCompletion method is an ActiveRecord::Relation. You are then asking if this relation exists by placing this inside a conditional. This will always be true, even if the relation contains an empty data set (e.g. []), because an empty Array is truthy.
You should be expecting getCompletion to return a collection of HomeworkStudent records. If you are wanting to show a tick, or an 'x' only if all students have completed the homework, then you need an aggregate method to check something about all of the records (if there are any, or none, etc). Try do something like:
# app/models/homework.rb
def completed?
homework_students.where("completed_on is null").none?
end
# in the view
<% if homework.completed? %>
<%= image_tag "fi-check.svg", class: "homework_student_complete" %>
<% else %>
<%= image_tag "fi-x.svg", class: "homework_student_complete" %>
<% end %>
More information on ActiveRecord::Relation methods at http://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-none-3F
The result of your query can be an ActiveRecord::Relation or an empty array and on both cases the if statement evaluates to true. Try changing your query to:
def completion
homework_students.where("completed_on is null").length == 0
end
your method get_completion is not returning anything. Try this
#homework_student = homework_students.where("completed_on is not null")
return #homework_student
may be it works. Also make sure that the completed_on is not null works correctly.
Related
I am newbie. I am trying to develop a simple web application involving shops and candies where a shop can have many candies.
I have the following code in my shops/show.html.erb which displays list of candies twice.
<% i=0 %>
<% for candy in #shop.candies do %>
<% i+=1 %>
<%= i %> <%= candy.name %>
<% end %>
<%= form_for([#shop, #shop.candies.build]) do |f| %>
<%= render(:partial => 'form',
:locals => {:f => f, :header => "Add a Candy",
:placeholder => "Enter a candy name"}) %>
<% end %>
<% i=0 %>
<% for candy in #shop.candies do %>
<% i+=1 %>
<%= i %> <%= candy.name %>
<% end %>
My code in _form.html.erb for creating a new candy:
<%= f.text_field(:name, :placeholder=> placeholder, :class=>"form-control custom-input")%>
<%= button_tag( :class => "btn btn-primary mb-2 btn-custom btn-custom-sc") do %>
<i class="fas fa-plus icon"></i>
<% end %>
Code of Shops Controller:
class ShopsController < ApplicationController
def show
#shop = Shop.find(params[:id])
#unshelved_candies = #shop.candies.unshelved_candies
end
private
def shop_params
params.require(:shop).permit(:name)
end
end
Code of Candies Controller:
class CandiesController < ApplicationController
def create
#shop = Shop.find(params[:shop_id])
#candy = #shop.candies.create(candy_params)
redirect_to(shop_path(#shop))
end
private
def candy_params
params.require(:candy).permit(:name)
end
end
end
When I run the code and view it on browser, I notice that it creates an empty candy in the second loop (not in database). However, when I remove the form for creating candies, it behaves as it should. I am unable to understand why it's looping one more time and displaying blank value.
The output of the first loop is the correct one:
Candy 1
Candy 2
Candy 3
And the output of second loop is:
Candy 1
Candy 2
Candy 3
[<---Empty. I am not inserting anything new to the database]
Can anybody tell me why it is displaying a blank value in second loop and how to prevent this extra iteration?
I believe the "extra" candy is the one you're instantiating here:
<%= form_for([#shop, #shop.candies.build]) do |f| %>
The candy name for the new candy is nil, so you're getting the blank.
BTW, this:
<% i=0 %>
<% for candy in #shop.candies do %>
<% i+=1 %>
<%= i %> <%= candy.name %>
<% end %>
Strikes me as non-idiomatic ruby. I would expect to see something more like:
<% #shop.candies.each.with_index(1) do |candy, index| %>
<%= index %> <%= candy.name %>
<% end %>
I guess a brute force way of making sure you don't get that extra candy would be to do something like:
<% #shop.candies.each.with_index(1) do |candy, index| %>
<% unless candy.new_record? %>
<%= index %> <%= candy.name %>
<% end %>
<% end %>
You might also try:
<%= form_for([#shop, #candy]) do |f| %>
Which I believe can be written:
<%= form_for(#shop, #candy) do |f| %>
If you want to save yourself a couple of key strokes (they add up over time).
And then in your ShopsController do:
class ShopsController < ApplicationController
def show
#shop = Shop.find(params[:id])
#candy = Candy.new
#unshelved_candies = #shop.candies.unshelved_candies
end
private
def shop_params
params.require(:shop).permit(:name)
end
end
This is also nice because it avoids:
#shop.candies.build
Which requires that your view knows a lot about the relationship between shop and candies and also requires that your view interacts directly with the database.
Since you're apparently using nested routes, you might want to look at the shallow: true directive.
Also (this is not related to your question), you might want to be thoughtful about the Law of Demeter. I notice you do:
#unshelved_candies = #shop.candies.unshelved_candies
Personally, I would do something more like:
#unshelved_candies = #shop.unshelved_candies
And in Shop, you might have something like:
class Shop < ApplicationRecord
def unselved_candies
candies.unshelved
end
end
And in Candy, something like:
class Candy < ApplicationRecord
class < self
def unshelved
where(shelved: false) # or however you determine a candy is unshelved
end
end
end
Many people would make unshelved a scope, which is another way of doing the same thing.
This way, your ShopsController knows less about the mechanics of the relationships between shops and candies and shelved status. FWIW.
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.
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.
I'm having belongs_to / has_many relationship between Trainer and Sportists. I'm trying to loop through their values in view like this:
<% #sportists.each do |s| %>
<%= s.name %> <%= s.surname %>
<%= s.trainer.city %>
<% end %>
and the Sportist related information works fine, but the trainers - doesn't. I get the error given in the title. If I try to do this in rails console everything works, so relationships should be set fine.
Things I've tried:
<% s.trainers.each do |t| %>
<%= t.city %>
<% end %>
that gives me undefined method 'trainers' error, and if I try s.trainer I get
#<TRAINER:0X00000004CE7CB0>
So what could be the fix?
EDIT
My models:
Trainer
has_many :sportists
belongs_to :team
accepts_nested_attributes_for :sportists, :reject_if => :all_blank, :allow_destroy => true
Sportist
belongs_to :trainer
Controller
#sportists = Sportist.all
You are getting undefined method 'city' for nil:NilClass in below code:
<% #sportists.each do |s| %>
<%= s.name %> <%= s.surname %>
<%= s.trainer.city %>
<% end %>
which means that there is a sportists record which doesn't have trainer associated to it.
So, for that particular sportlist record s.trainer is nil and you cannot call city on nil object.
To identify the sportist record for which you don't have an associated trainer, just update the view code as below:
<% #sportists.each do |s| %>
<%= s.name %> <%= s.surname %>
<%= s.trainer.try(:city) %>
<% end %>
This way even if you don't have an associated trainer record, error would not be raised.
In the rendered view, just look for sportlist record which doesn't show any city, that would be the sportlist record without an associated trainer.
As for the second error undefined method 'trainers' that you received on
<% s.trainers.each do |t| %>
<%= t.city %>
<% end %>
sportlist belongs_to trainer, you only have dynamic method trainer (NOTE singular) available and NOT trainers (NOTE plural). Also, s.trainer would return a single trainer record so you cannot iterate over it with each method as it is not a collection BUT a single record.
UPDATE
Ideally, you should not have allowed creation of sportist records without a trainer.
You should have added an index on the foreign key trainer_id created on sportlists table. With this you don't even have to use try method and your current code would work as it is.
You can make use of delegate and avoid use of try, if and terniary operator.
Sportist
belongs_to :trainer
delegate :city, to: :trainer, :allow_nil => true
You need to make small change to your existing code and it will work smoothly :)
<% #sportists.each do |s| %>
<%= s.name %> <%= s.surname %>
<%= s.city %>
<% end %>
Seems like you have a sportists that doesn't have a trainer. To avoid this make an if condition like this.
<% #sportists.each do |s| %>
<%= s.name %> <%= s.surname %>
<%= s.trainer.city if s.trianer.present?%>
<% end %>
And also setting the validation in the Sportist model should resolve the empty trainer_id values
Class Sportist < ActiveRecord::Base
belongs_to :trainer
validates :trainer_id, presence: true
end
You can update your code
<% #sportists.each do |s| %>
<%= s.name %> <%= s.surname %>
<%= s.trainer.city %>
<% end %>
with
<% #sportists.each do |s| %>
<%= s.name %> <%= s.surname %>
<%= s.trainer.present? ? s.trainer.city : "No City Found" %>
<% end %>
This will stop the code to throw nil errors
I am trying to display the output of this find -
#test = User.joins(:plans => [:categories => [:project => :presentations]]).where(current_user.id)
Here is my output loop
<% #test.each do |p| %>
<%= p.plans %>
<% p.plans.each do |d| %>
<%= debug(d) %>
<% d.categories.each do |e| %>
<% e.project.each do |r| %>
<%= debug(r) %>
<% end %>
<% end %>
<% end %>
<% end %>
The loop works until it gets to project when it throws this error
undefined method `each' for "#<Project:0x000001033d91c8>":Project
If I change it to projects in the loop it gives this error
undefined method `projects' for #<Plan:0x000001033da320>
The debug at categories level shows this
--- !ruby/object:Category
attributes:
id: 2
name: test
short_name: tst
created_at:
updated_at:
category_id: 2
plan_id: 5
My relationships look like this
User
has_many :user_plans
Plan
has_many :user_plans
has_and_belongs_to_many :categories
Category
has_one :project
has_and_belongs_to_many :plans
Project
has_many :presentations, :dependent => :delete_all
Presentation
belongs_to :project
Do I need to changed my find ?
Thanks, Alex
Category has_one :project
so it is single object not collection thus no each method.
According to your relationship definitions, Category only has_one project, so why do you want to iterate over e.project? If you just want to show debugging output, replace
<% e.project.each do |r| %>
<%= debug(r) %>
<% end %>
with
<%= debug(e.project) %>
But if you want to go deeper, into presentations, do:
<%= debug(e.project) %>
<% e.project.presentations.each do |presentation| %>
<%= debug(presentation) %>
<% end %>
Your problem is that you are calling the array method .each on a single object.
category.project will give you a single Project object right? That's not an array, so you can't call each on it.
Replace this:
<% e.project.each do |r| %>
<%= debug(r) %>
<% end %>
with
debug(e.project)
While you're at it, here's some other advice: use descriptive variable names. Why does 'p' represent a test, 'd' represent a plan, 'e' represent a category, etc? Variable names should tell you what the object is. Similarly, i'd expect the variable #test to hold a Test object. In your code it seems to be an array. Use plural variable names for a variable that holds a collection of that type of object - eg #plans would be an array of Plan objects.
eg
<% #tests.each do |test| %>
<% test.plans.each do |plan| %>
<%= debug(plan) %>
<% plan.categories.each do |category| %>
<%= debug(category.project) %>
<% end %>
<% end %>
<% end %>
Isn't that more readable?