Rails 4 - post completion evaluations model - structure - ruby-on-rails

I'm still feeling my way with Rails.
I'm trying to add an evaluation function to my projects based app.
I want each project participant to submit an evaluation when a project is complete.
I have an evaluation model with:
Evaluation.rb
# == Schema Information
#
# Table name: evaluations
#
# id :integer not null, primary key
# user_id :integer
# evaluatable_id :integer
# evaluatable_type :string
# overall_score :integer
# project_score :integer
# personal_score :integer
# remark :text
# work_again? :boolean
# continue_project? :boolean
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_evaluations_on_evaluatable_type_and_evaluatable_id (evaluatable_type,evaluatable_id) UNIQUE
#
class Evaluation < ActiveRecord::Base
# --------------- associations
belongs_to :evaluator, :polymorphic => true, class_name: 'Evaluation'
belongs_to :evaluatable, :polymorphic => true, class_name: 'Evaluation'
# --------------- scopes
# --------------- validations
# --------------- class methods
# --------------- callbacks
# --------------- instance methods
# --------------- private methods
end
I have concerns for:
module Evaluatable
extend ActiveSupport::Concern
included do
has_many :received_evaluations, as: :evaluatable, dependent: :destroy, class_name: 'Evaluation'
end
end
module Evaluator
extend ActiveSupport::Concern
included do
has_many :given_evaluations, as: :evaluator, dependent: :destroy, class_name: 'Evaluation'
end
end
I'm then trying to show each user's evaluations (received) as:
<% Evaluation.find(params[:id]).evaluation.order('created_at DESC').each do |eval| %>
<div id="portfolioFiltering" class="masonry-wrapper row">
<%= eval.remark %>
<%= eval.personal_score %>
<small><%= eval.created_at %></small>
</div>
<% end %>
But I get this error:
undefined method `evaluations' for #<Evaluation:0x007ff274b13b90>
Did you mean? evaluator
evaluator=
I'm not even sure I've understood the error message, let alone figured out what to do about it.
Can anyone make sense of this message?

Remove this from your relations
:polymorphic => true,
Here is a article when we should use polymorphics relations.
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Related

RoR How to properly set fields_for?

I've got this whole surveys thing:
Survey.rb
# frozen_string_literal: true
# == Schema Information
#
# Table name: surveys
#
# id :bigint not null, primary key
# description :string
# hidden :boolean default(FALSE)
# internal_description :string
# internal_name :string
# min_green :integer
# min_orange :integer
# min_red :integer
# required :boolean default(FALSE)
# survey_type :integer default("medical")
# title :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Survey < ApplicationRecord
has_many :questions, class_name: 'Question', dependent: :destroy
has_many :filled_surveys
has_many :study_surveys
has_many :studies, through: :study_surveys
accepts_nested_attributes_for :questions
enum survey_type: %i[medical law]
validates :title, :description, :internal_name, :internal_description, presence: true
end
filled_survey.rb
# frozen_string_literal: true
# == Schema Information
#
# Table name: filled_surveys
#
# id :bigint not null, primary key
# score :integer
# state :integer default("pending")
# created_at :datetime not null
# updated_at :datetime not null
# survey_id :bigint not null
# user_id :bigint not null
#
# Indexes
#
# index_filled_surveys_on_survey_id (survey_id)
# index_filled_surveys_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (survey_id => surveys.id)
# fk_rails_... (user_id => users.id)
#
class FilledSurvey < ApplicationRecord
belongs_to :survey
has_many :questions, through: :survey
belongs_to :user, class_name: 'User'
has_many :answers, class_name: 'Answer'
accepts_nested_attributes_for :answers
has_one :study, through: :survey
enum state: %i[pending done redo]
end
question.rb
# frozen_string_literal: true
# == Schema Information
#
# Table name: questions
#
# id :bigint not null, primary key
# description :string
# max :integer
# min :integer
# order :integer default(0)
# placeholder :string
# question_type :integer
# title :string
# created_at :datetime not null
# updated_at :datetime not null
# survey_id :bigint not null
#
# Indexes
#
# index_questions_on_survey_id (survey_id)
#
# Foreign Keys
#
# fk_rails_... (survey_id => surveys.id)
#
class Question < ApplicationRecord
belongs_to :survey
has_many :question_options, class_name: 'QuestionOption', dependent: :destroy
has_many :answers, dependent: :destroy
accepts_nested_attributes_for :question_options, allow_destroy: true, reject_if: proc { |att| att['name'].blank? }
enum question_type: %i[short long number single multiple date]
validates :title, presence: true, on: :create
end
question_option.rb
# frozen_string_literal: true
# == Schema Information
#
# Table name: question_options
#
# id :bigint not null, primary key
# display :string
# name :string
# score :integer default(0)
# created_at :datetime not null
# updated_at :datetime not null
# question_id :bigint not null
#
# Indexes
#
# index_question_options_on_question_id (question_id)
#
# Foreign Keys
#
# fk_rails_... (question_id => questions.id)
#
class QuestionOption < ApplicationRecord
belongs_to :question, optional: true
validates :name, :display, :score, presence: true
end
answer.rb
# frozen_string_literal: true
# == Schema Information
#
# Table name: answers
#
# id :bigint not null, primary key
# content :string
# created_at :datetime not null
# updated_at :datetime not null
# filled_survey_id :bigint not null
# option_id :bigint
# question_id :bigint not null
#
# Indexes
#
# index_answers_on_filled_survey_id (filled_survey_id)
# index_answers_on_option_id (option_id)
# index_answers_on_question_id (question_id)
#
# Foreign Keys
#
# fk_rails_... (filled_survey_id => filled_surveys.id)
# fk_rails_... (question_id => questions.id)
#
class Answer < ApplicationRecord
belongs_to :filled_survey
belongs_to :question
belongs_to :option, class_name: 'QuestionOption', optional: true
validates :content, presence: true, length: { minimum: 1 }, on: :update
validate :content, :validate_question_rules, on: :update
end
And view:
/ Body
.w-full.flex-grow.overflow-y-auto
/ Surveys questions forms
= form_for #selected, url: update_filled_survey_path(#selected.id), html: { data: { 'participant-target' => "#{#selected.id}_form" } } do |form|
= form.hidden_field :survey_id
- #selected.questions.includes(:question_options).each do |question|
.rounded-md.bg-gray-100.opacity-75.flex.items-center.justify-between.mb-2.p-2.relative
%span.flex-grow
= render "components/inputs/#{question.question_type}", question: question, form: form
.flex.justify-center.bg-gray-100.rounded-md.cursor-pointer.my-3
%button.w-full.px-6.py-2.bg-green-200.rounded-md.cursor-pointer.hover:bg-green-300{'data-action' => 'click->participant#updateSurvey', 'data-filled' => #selected.id } #{t('views.participant_portal.surveys.complete')}
Partial(for single type question):
.input_group.flex.flex-col.justify-start
%label.text-gray-700.text-md.font-bold
= question.title
- if question.description
%label.text-gray-600.text-sm #{question.description}
.p-4
= form.fields_for question.question_options do |question_form|
= question_form.collection_select(:option_id, question.question_options, :id, :display, { selected: question.question_options.option_id, include_blank: false }, { id: question.id, name: "#{question.id}_answer", class: 'bg-transparent mx-2'})
JS controller (stimulus JS):
updateSurvey (e) {
document.getElementsByTagName('form')[0].submit()
}
Controller
before_action :set_filled, only: %i[fill_survey]
def set_filled
if current_user.profile.active_study && current_user.profile.paid?
#surveys = current_user.profile.active_study.surveys.select(&:medical).map do |survey|
FilledSurvey.includes.find_or_create_by(survey: survey, user: current_user)
end.reject(&:done?)
elsif current_user.profile.active_study && current_user.profile.approved?
#surveys = current_user.profile.active_study.surveys.select(&:medical?).map do |survey|
FilledSurvey.find_or_create_by(survey: survey, user_id: current_user.id,
created_at: FilledSurvey.this_month)
end.reject(&:done?)
elsif action_name == 'law_quiz'
#surveys = current_user.profile.active_study.surveys.select(&:law?).map do |survey|
FilledSurvey.find_or_create_by(survey: survey, user_id: current_user.id,
created_at: FilledSurvey.this_month)
end.reject(&:done?)
end
end
def fill_survey
set_meta_tags(title: t('page_title.participant_portal.fill_survey'))
aasm_state = current_user.profile.aasm_state
redirect_to authenticated_root_path unless %w[paid approved].include?(aasm_state) &&
!#surveys.empty?
#active_study = current_user.profile.active_study
#selected = params[:id] ? FilledSurvey.find(params[:id]) : #surveys.first
# #answers = #selected.survey.questions.map do |question|
# Answer.find_or_create_by(question_id: question.id, filled_survey_id: #selected.id)
# end
# #answers.sort_by { |answer| answer.question.order }
end
What I wanted to do:
Submit all forms with one button
Get ID of QuestionOption that I select for each question
Create Answer for each Question based on QuestionOption
What I get:
"undefined method `option_id' for #<ActiveRecord::Associations::CollectionProxy [#<QuestionOption id: 328, name:(...)"
Can someone explain how to make it works? I want to understand what's wrong with this code, so I can implement this right way

How do I elegantly handle two-sided relations with Rails translations?

I have a family_tree and someone can add their relatives to the tree.
So what happens is there is a membership record created for each family_tree entry.
However, if a Son adds a Dad, we should be able to update the family tree of the Dad to add the "Son" to the tree in the view. What's the best Rails way to approach this? I know Rails does a lot of translations natively, and pluralizations, etc. Anyway for me to leverage that for what I want to do?
Also, what is the class/module that handles that stuff again? ActiveSupport?
This is my User model:
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# email :string(255) default(""), not null
# encrypted_password :string(255) default(""), not null
# reset_password_token :string(255)
# reset_password_sent_at :datetime
# remember_created_at :datetime
# sign_in_count :integer default(0), not null
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# current_sign_in_ip :string(255)
# last_sign_in_ip :string(255)
# created_at :datetime
# updated_at :datetime
# name :string(255)
# confirmation_token :string(255)
# confirmed_at :datetime
# confirmation_sent_at :datetime
# unconfirmed_email :string(255)
# invitation_relation :string(255)
# avatar :string(255)
#
class User < ActiveRecord::Base
has_one :family_tree, dependent: :destroy
has_many :memberships, dependent: :destroy
has_many :nodes, dependent: :destroy
has_many :participants, dependent: :destroy
end
FamilyTree.rb
# == Schema Information
#
# Table name: family_trees
#
# id :integer not null, primary key
# name :string(255)
# user_id :integer
# created_at :datetime
# updated_at :datetime
#
class FamilyTree < ActiveRecord::Base
belongs_to :user
has_many :memberships, dependent: :destroy
has_many :members, through: :memberships, source: :user, dependent: :destroy
has_many :nodes, dependent: :destroy
end
Membership.rb:
# == Schema Information
#
# Table name: memberships
#
# id :integer not null, primary key
# family_tree_id :integer
# user_id :integer
# created_at :datetime
# updated_at :datetime
# relation :string(255)
#
class Membership < ActiveRecord::Base
belongs_to :family_tree
belongs_to :user
end
Node.rb
# == Schema Information
#
# Table name: nodes
#
# id :integer not null, primary key
# name :string(255)
# family_tree_id :integer
# user_id :integer
# media_id :integer
# media_type :string(255)
# created_at :datetime
# updated_at :datetime
# circa :datetime
# is_comment :boolean
#
class Node < ActiveRecord::Base
belongs_to :family_tree
belongs_to :user
belongs_to :media, polymorphic: true, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :node_comments, dependent: :destroy
end
My _tree.html.erb looks like this (truncated for brevity):
<li class="tree-item-name">Great Grandparents
<ul>
<li><% if relative.humanize == "Great Grandfather" || relative.humanize == "Great Grandmother" %>
<%= link_to image_tag(membership.user.avatar.url, size: "48x48", :class => "img-circle") , family_tree_path(membership.user.family_tree), :target => '_blank' %>
<%= link_to membership.user.name, family_tree_path(membership.user.family_tree), :target => '_blank'%>
<% else %>
None added yet, add them <%= link_to 'here', "#" , class: 'btn invite popupbox','data-popup' => 'invite_friend' %>
<% end %>
</li>
</ul>
</li>
<li class="tree-item-name">Grandparents
<ul>
<li><% if relative.humanize == "Grandfather" || relative.humanize == "Grandmother" %>
<%= link_to image_tag(membership.user.avatar.url, size: "48x48", :class => "img-circle") , family_tree_path(membership.user.family_tree), :target => '_blank' %>
<%= link_to membership.user.name, family_tree_path(membership.user.family_tree), :target => '_blank' %>
<% else %>
None added yet, add them <%= link_to 'here', "#" , class: 'btn invite popupbox','data-popup' => 'invite_friend' %>
<% end %>
</li>
</ul>
</li>
I would use the same relations you defined in the question, except this part:
class Membership < ActiveRecord::Base
belongs_to :family_tree
belongs_to :user_one, class_name: 'User'
belongs_to :user_two, class_name: 'User' # I actually have no idea how to call them!
belongs_to :relation # to hold values likes 'Son', 'Dad', etc.
# The model Relation would be as simple as a name and internal reference, nothing else.
# (internal_reference is here to solve the translation problems and other stuff you will understand with the following code)
With a callback after_create to reverse the membership created:
def create_reverse_membership
user_one_is_female = user_one.gender == 'female'
user_two_is_female = user_two.gender == 'female'
son_or_daughter = user_one_is_female ? :daughter : :son
father_or_mother = user_two_is_female ? :mother : :father
case relation.internal_reference.to_sym
when :son
relation = Relation.find_by_internal_reference(father_or_mother)
membership = Membership.where(relation_id: relation.id, user_one: user_two.id, user_two: user_one.id).first
if membership.present?
# This means the reverse membership already exists, do not call Membership.create here because it would cause and endless loop with the callback
else
membership = Membership.create(relation_id: relation.id, user_one: user_two, user_two: user_one)
end
when :father
# almost same logic but with `son_or_daughter`
when :mother
else
end
end
English not being my native language, this code probably lacks of consistency (coherence, logic).
Hope this helps!

Validations misfiring in a form with multiple models

I'm building a web app that saves a user's goals and tasks, where a user has_many goals, and a goal has_many tasks. When I try to save a goal and task together, I keep getting validation errors saying "Tasks goal can't be blank" and "Tasks content can't be blank," even though they clearly aren't. I'm certain the problem isn't with the actual form, but with the goal controller's 'new' or 'create' code, but whatever I try, I can't seem to get it right. Any ideas on why the validations for the task model are misfiring? I've been working on this issue for too long and I'm about to give up. I've included the goal controller, goal model, task model, and debug info. If you need to see any other code, let me know.
Goal Controller:
def new
#title = "New Goal"
#goal = Goal.new
#goal.tasks.build
end
def create
#user = current_user
#goal = #user.goals.build(params[:goal])
#task = #goal.tasks.build(params[:goal][:task])
if #goal.save
flash[:success] = "Goal created!"
redirect_to user_path(#user)
else
render 'new'
end
end
Goal Model:
# Table name: goals
#
# id :integer not null, primary key
# user_id :integer
# content :string(255)
# completed :boolean
# completion_date :date
# created_at :datetime
# updated_at :datetime
#
class Goal < ActiveRecord::Base
attr_accessible :content, :completed, :completion_date
belongs_to :user
has_many :tasks, :dependent => :destroy
accepts_nested_attributes_for :tasks
validates :user_id, :presence => true
validates :content, :presence => true, :length => { :maximum => 140 }
end
Task Model:
# id :integer not null, primary key
# goal_id :integer
# content :string(255)
# occur_on :date
# recur_on :string(255)
# completed :boolean
# completion_date :date
# created_at :datetime
# updated_at :datetime
#
class Task < ActiveRecord::Base
attr_accessible :content, :occur_on, :recur_on, :completed
belongs_to :goal
validates :goal_id, :presence => true
validates :content, :presence => true, :length => { :maximum => 140 }
end
Debug Dump after an unsuccessful save:
--- !map:ActiveSupport::HashWithIndifferentAccess
utf8: "\xE2\x9C\x93"
authenticity_token: NF/vVwinKQlGAvBwEnlVX/d9Wvo19MipOkYb7qiElz0=
goal: !map:ActiveSupport::HashWithIndifferentAccess
content: some goal
tasks_attributes: !map:ActiveSupport::HashWithIndifferentAccess
"0": !map:ActiveSupport::HashWithIndifferentAccess
content: some task
commit: Submit
action: create
controller: goals
This is a problem with nested attributes. You cannot validate the presence of the encapsulating model from the nested model (in your case, you cannot validate the presence of goal_id from Task). When validations are run the goal is not yet saved, and thus has no id, so it is impossible to assign it.
You can either eliminate the validation that is causing the problem, or not use the built-in nested attributes. In the latter case, you would need to add your own logic to first save the goal and then create any nested tasks.
Bummer, huh? I wish someone would come up with a good solution for this...maybe I'll work on it if I ever get some free time.

Creating Form with accepts_nested_attributes_for

I have 2 models, a User and Patient. A User HAS_ONE Patient and a Patient BELONGS_TO a User.
class Patient < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user
attr_accessible :user_id, :user_attributes
end
# == Schema Information
#
# Table name: patients
#
# id :integer not null, primary key
# user_id :integer
# insurance :string(255)
# created_at :datetime
# updated_at :datetime
#
class User < ActiveRecord::Base
has_one :patient
attr_accessible :username, :password, :active, :disabled, :first_name, :last_name,
:address_1, :address_2, :city, :state, :postcode, :phone, :cell, :email
attr_accessor :password
end
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# username :string(255)
# encrypted_password :string(255)
# salt :string(255)
# active :boolean
# disabled :boolean
# last_login :time
# first_name :string(255)
# last_name :string(255)
# address_1 :string(255)
# address_2 :string(255)
# city :string(255)
# state :string(255)
# postcode :string(255)
# phone :string(255)
# cell :string(255)
# email :string(255)
# created_at :datetime
# updated_at :datetime
#
In my patients controller I am trying to create a new Patient form.
class PatientsController < ApplicationController
def new
#patient = Patient.new
end
end
In My View (new.html.erb)
<%= form_for #patient do |patient_form| %>
<% patient_form.fields_for :user do |user_fields| %>
<table class="FormTable" border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="label">
<%= user_fields.label :username %> *:
</td>
<td class="input">
<%= user_fields.text_field :username, :class=>"TextField" %>
</td>
</tr>
</table>
...
<%end%>
<%end%>
The form shows up blank with a submit button with no generated markup for the user_fields
I have been told I am doing this wrong because of the patient having the accepts_nested_attributes_for :user and it should be the user nesting the attributes BUT in my system I want to use the resources model so a patient and other user types are treated separately.
Example Database table:
USERS: id|first_name|last_name...etc
PATIENTS: id|user_id|insurance
Unless I'm mistaken, you have no user when you are calling fields_for. Before you can do the fields_for, you will need to have an instance of a user that can be used to build the form, kind of like how you have #patient for your patient_form.
Your best bet would be to build a User in your controller, based off of your #patient, and then you will have access to that in the view.
try <%= patient_form.fields_for with the equals sign in there? I know there was a warning message for awhile about "block style helpers are deprecated".
The answers from Jeff Casimir and theIV are correct, but you need to do them both. I.e., fix the patient_form.fields_for block to user <%=, and build a User object for the Patient in the controller, something like:
def new
#patient = Patient.new
#patient.user = User.new
end

Why am I getting a NoMethodError for an attribute that exists in my model?

This is the error I get:
ContactPostalcardsController#skip (NoMethodError) "undefined method `status=' for #<ContactPostalcard:0x2b21433d64b0>"
This is the code calling it and trying to assign a value to the status attribute for ContactPostalcard (the Model):
def skip
#contact_postalcard = ContactPostalcard.new(params[:contact_postalcard])
#contact_postalcard.contact_id = params[:contact_id]
#contact_postalcard.postalcard_id = params[:postalcard_id]
#contact_postalcard.status = "skipped"
#contact_postalcard.date_sent = Date.today
#contact_postalcard.date_created = Date.today
if #contact_postalcard.save
render :text => 'This email was skipped!'
end
end
This is the Model referred. Note the "annotate" output shows status as an attribute:
class ContactPostalcard < ActiveRecord::Base
attr_accessible :title, :contact_id, :postal_id, :postalcard_id, :message, :campaign_id, :date_sent, :status
belongs_to :contact
belongs_to :postalcard
alias_attribute :body, :message
alias_attribute :subject, :title
named_scope :nosugar, :conditions => { :sugarcrm => false }
def company_name
contact = Contact.find_by_id(self.contact_id)
return contact.company_name
end
def asset
Postalcard.find_by_id(self.postalcard_id)
end
def asset_class
Postalcard.find_by_id(self.postalcard_id).class.name
end
end
# == Schema Information
#
# Table name: contact_postalcards
#
# id :integer not null, primary key
# title :string(255)
# contact_id :integer
# postalcard_id :integer
# message :text
# campaign_id :integer
# date_sent :datetime
# created_at :datetime
# updated_at :datetime
# postal_id :integer
# sugarcrm :boolean default(FALSE)
# status :string(255)
#
I am unclear as to why I keep getting an 'undefined method' -- I have added the status attribute (it had been missing before but used a migration and then raked), so need some help...thank you.
Have you restarted your Rails application since you ran your migration? If you're running in production mode, Rails caches your classes until you restart it, and since status wasn't an attribute before the migration, Rails wouldn't have added accessor methods for it, which would explain why status= is undefined.

Resources