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

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!

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

Rails 4 - post completion evaluations model - structure

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

SQLite3::SQLException: no such column: [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
undefined method `to_f' for #<ActiveRecord::Relation:0x472d0a0>
I'm trying to make a call tracking application to learn twilio and rails.
Right now, I would like to make a graph that shows a user how many phone calls a particular phone number gets per day.
The schema is user has_many phones has_many calls.
I try to make the graph by creating an instance method that counts the number of phones on a particular day, but when I try executing the code, I get the error :
SQLite3::SQLException: no such column: calls.placed_at: SELECT COUNT(*) FROM "calls" WHERE "calls"."phone_id" = 44 AND ("calls"."placed_at" BETWEEN '2012-09-15 00:00:00.000000' AND '2012-09-15 23:59:59.999999')
I don't quite understand the code I'm using for the instance method, and it's probably calling the wrong column. Your help on this would be greatly appreciated.
Here's the important part of my call model:
def total_on(date)
calls.where(placed_at: date.beginning_of_day..date.end_of_day).count
end
Here's how I'm counting the phone calls in my show view
<%= (1.month.ago.to_date..Date.today).map { |date| #phone.total_on(date).to_f}.inspect %>
Here's how I define the #phone variable
#phone = Phone.find_by_id(params[:id])
Here's my complete phone model (for schema reference)
# == Schema Information
#
# Table name: phones
#
# id :integer not null, primary key
# name :string(255)
# twilio_number :integer
# original_number :integer
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Phone < ActiveRecord::Base
attr_accessible :original_number, :user_id, :name, :twilio_number
belongs_to :user
has_many :calls, dependent: :destroy
validates :name, presence: true
validates :twilio_number, presence: true
validates :original_number, presence: true
validates :user_id, presence: true
default_scope order: 'phones.created_at DESC'
validate :check_phone_limit, :on => :create
def check_phone_limit
if User.find(self.user_id).at_max_phone_limit?
self.errors[:base] << "Cannot add any more phones"
end
end
def original_number=(value)
num = value.to_s.gsub(/[^0-9+]/, "")
write_attribute(:original_number, num.to_i)
end
def total_on(date)
calls.where(placed_at: date.beginning_of_day..date.end_of_day).count
end
end
Here's my complete call model
# == Schema Information
#
# Table name: calls
#
# id :integer not null, primary key
# AccountSid :string(255)
# From :string(255)
# To :string(255)
# CallStatus :string(255)
# ApiVersion :string(255)
# Direction :string(255)
# FromCity :string(255)
# FromState :string(255)
# FromZip :string(255)
# FromCountry :string(255)
# ToCity :string(255)
# ToState :string(255)
# ToZip :string(255)
# ToCountry :string(255)
# CallSid :string(255)
# DialCallSid :string(255)
# DialCallDuration :string(255)
# DialCallStatus :string(255)
# RecordingUrl :string(255)
# phone_id :integer
# DialCallMinutes :integer
# created_at :datetime
# updated_at :datetime
#
class Call < ActiveRecord::Base
attr_accessible :AccountSid, :From, :To, :CallStatus, :ApiVersion, :Direction, :FromCity, :FromState, :FromZip, :FromCountry, :ToCity, :ToState, :ToZip, :ToCountry, :CallSid, :DialCallSid, :DialCallDuration, :DialCallStatus, :RecordingUrl, :DialCallMinutes
belongs_to :phone
def self.create_from_incoming_call(params)
user_phone = Phone.find_by_twilio_number(params['To']) #Finds the phone number in the database based on what phone Twilio is calling
twilio_request_params = {
:CallSid => params['CallSid'],
:AccountSid => params['AccountSid'],
:From => params['From'],
:To => params['To'],
:CallStatus => params['CallStatus'],
:ApiVersion => params['ApiVersion'],
:Direction => params['Direction'],
:FromCity => params['FromCity'],
:FromState => params['FromState'],
:FromZip => params['FromZip'],
:FromCountry => params['FromCountry'],
:ToCity => params['ToCity'],
:ToState => params['ToState'],
:ToZip => params['ToZip'],
:ToCountry => params['ToCountry']
:phone_id => user_phone.phone_id
}
call = Call.new(twilio_request_params)
call.save
return call
end
def Call.update_dial_call(params)
twilio_request_params = {
:DialCallSid => params['DialCallSid'],
:DialCallDuration => params['DialCallDuration'],
:DialCallStatus => params['DialCallStatus'],
:RecordingUrl => params['RecordingUrl'],
:DialCallMinutes => (params['DialCallDuration'].to_f/60.to_f).ceil
}
call = Call.where( :CallSid => params['CallSid'] ).first
call.update_attributes twilio_request_params
call.save
end
end
I've been stuck on this for a while; any help would be greatly appreciated!
Your call model uses the standard rails created_at, yet your query was using placed_at, which doesn't exist.

How to render json using :includes in it?

I've models GuestOrder, OrderBatch, OrderItem
# == Schema Information
#
# Table name: guest_orders
#
# id :integer not null, primary key
# notes :string(255)
# adults :integer
# children :integer
# created :datetime
# placed :datetime
# billed :datetime
# created_at :datetime
# updated_at :datetime
#
class GuestOrder < ActiveRecord::Base
has_many :order_batches, :dependent => :destroy
end
# == Schema Information
#
# Table name: order_batches
#
# id :integer not null, primary key
# placed :datetime
# guest_order_id :integer
# created_at :datetime
# updated_at :datetime
#
class OrderBatch < ActiveRecord::Base
belongs_to :guest_order
has_many :order_items, :dependent => :destroy
end
# == Schema Information
#
# Table name: order_items
#
# id :integer not null, primary key
# quantity :integer
# accepted :datetime
# cooking :datetime
# ready :datetime
# delivered :datetime
# cancelled :datetime
# order_batch_id :integer
# dish_id :integer
# created_at :datetime
# updated_at :datetime
#
class OrderItem < ActiveRecord::Base
belongs_to :order_batch
belongs_to :dish
end
I'm trying to render json in the following method to get a guest_order and its belonging order_batches and order_items by passing guest_order id as parameter.
def getOrderDetails
#To get the details of a particular guest_order and its batches and items
#guest_order = GuestOrder.find_by_id(params[:id])
render :json => #guest_order.to_json(:except => [:created_at, :updated_at],
:includes => {:order_batches => {:except => [:guest_order_id, :created_at, :updated_at],
:includes => {:order_items => {:except => [:order_batch_id, :created_at, :updated_at] } } } } )
end
But I didn't get the expected result, only the details from the guest_orders table is rendered. How to solve this?
I use :include rather than :includes, not sure if that's significant.
Try using the :include without the :except first, and when you get that working, add in the :except.

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

Resources