Can't show information from another model - ruby-on-rails

I did an application in rails and I'm trying to use a column type_id in relationship with my other table
My tables:
sinisters
+---+----------+---------+
|id | client | type_id |
+---+----------+---------+
| 1 | Charles | 1 |
| 2 | Montalvo | 1 |
| 3 | Gabriel | 2 |
| 4 | Miguel | 2 |
+---+----------+---------+
sinister_types
+----+--------+
| id | name |
+----+--------+
| 1 | DANGER |
| 2 | FIRE |
+----+--------+
My controller:
#sinisters = Sinister.find(:all)
My models:
class Sinister < ActiveRecord::Base
belongs_to :sinister_type
end
class SinisterType < ActiveRecord::Base
has_many :sinisters
end
My view:
<% #sinisters.each |sinister| do %>
<%= sinister.client %>
<%= sinister.type.name %>
<% end %>
I want to show sinister types names.
I tried this code and got nothing:
<%= sinister.sinister_type.name %>
And also tried and got nothing
<%= sinister.type.try(:name) %>
Please somebody can help me?

Like #backpackerhh says, the default convention is sinister_type_id, not type_id. But if you want to override it, you need to specify :foreign_key.
Model :
class Sinister < ActiveRecord::Base
belongs_to :sinister_type, :foreign_key => :type_id
end
class SinisterType < ActiveRecord::Base
has_many :sinisters
end
Controller :
#sinisters = Sinister.find(:all)
View :
Not #sinisters.each |sinister| do, but #sinisters.each do |sinister|
<% #sinisters.each do |sinister| %>
<%= sinister.client %> :
<%= sinister.sinister_type.name %>
<% end %>

I think your column on sinister table should be sinister_type_id (instead of type_id) or you need to specify the foreign key on your model.

Related

How to save parameters to a table sent by form_with method

I have this below page showing 3 questions, and I want to store the user input into a table called "user_answers" when submitted.
(test_one.html.erb)
<%= form_with(scope: #user_answer, url: answer_one_path, local: true) do |form| %>
<p>1. <%= #questions.all[0].question %></p>
<%= form.text_field :answer1 %> --> #I want this input saved in the table.
<p>2. <%= #questions.all[1].question %></p>
<%= form.text_field :answer2 %> --> #I want this input saved in the table.
<p>3. <%= #questions.all[2].question %></p>
<%= form.text_field :answer3 %> --> #I want this input saved in the table.
<%= form.submit "Submit!" %>
<% end %>
When submitted, it then moves to answer page, and I want it to store :answer1, :answer2, :answer3 into the column "user_answer" in "user_answers" table.
(Table "user_answers")
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| session_id | int(11) | YES | | NULL | |
| user_answer | varchar(255) | YES | | NULL | |
| question_id | int(11) | YES | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Here's my controller.
(homepage_controller.rb)
def test_one
#questions = QuestionAnswer.where(grade: 1, test_num: 1)
end
def answer_one
end
private
def all_answers
params.permit(:answer1, :answer2, :answer3)
end
Here's my model.
(user_answer.rb)
class UserAnswer < ApplicationRecord
end
(question_answer.rb)
class QuestionAnswer < ApplicationRecord
end
Here's my routes.rb.
Rails.application.routes.draw do
post "/answer_one", to: 'homepage#answer_one'
get "/answer_one", to: 'homepage#answer_one'
get "/test_one", to: 'homepage#test_one'
Here's the table "question_answers".
(question_answers table)
*************************** 1. row ***************************
id: 1
question: Color of apple? <-- question
answer: Red <-- answer
created_at: 2021-06-11 14:36:50
updated_at: 2021-06-11 14:36:50
grade: 1
test_num: 1
question_id: 1 <-- because it's question number 1
*************************** 2. row ***************************
id: 2
question: Color of banana? <-- question
answer: Yellow <-- answer
created_at: 2021-06-11 17:12:53
updated_at: 2021-06-11 17:12:53
grade: 1
test_num: 1
question_id: 2 <-- because it's question number 2
*************************** 3. row ***************************
id: 3
question: Color of Orange? <-- question
answer: Orange <-- answer
created_at: 2021-06-15 15:54:18
updated_at: 2021-06-15 15:54:18
grade: 1
test_num: 1
question_id: 3 <-- because it's question number 3
For example, if a user wrote "red" for :answer1, "yellow" for :answer2, "orange" for :answer3, I want those inputs to be saved with the question number as below;
(table "user_answers")
*************************** 1. row ***************************
id: 1
session_id: null
user_answer: red <-- # user input
question_id: 1 <-- # since it was question number 1
created_at: 2021-06-11 15:01:32
updated_at: 2021-06-11 15:01:32
*************************** 2. row ***************************
id: 1
session_id: null
user_answer: yellow <-- # user input
question_id: 2 <-- # since it was question number 2
created_at: 2021-06-11 15:01:32
updated_at: 2021-06-11 15:01:32
*************************** 2. row ***************************
id: 1
session_id: null
user_answer: orange <-- # user input
question_id: 3 <-- # since it was question number 3
created_at: 2021-06-11 15:01:32
updated_at: 2021-06-11 15:01:32
I attempted to include "create" method in the homepage_controller.rb as below, but the data didn't get saved in the user_answers table. What am I doing wrong / what am I not doing enough?
def create
#user_answer = UserAnswer.create(user_answer: all_answers, question_id: params[:id])
#user_answer.save
end
Any help is appreciated!!
This answer is NOT intended as a copy-paste solution. Its a nudge in the right direction and covers some pretty advanced topics. If you're new at rails you might want to set this up to just answer one question at a time and circle back later.
The Rails way to handle creating multiple resources in a single request is by using nested attributes. To start off you probally want some real tables for the domain instead of storing a bunch of denormalized strings:
# rails g model test title description:text
class Test < ApplicationRecord
has_many :questions
has_many :user_tests
has_many :user_answers, through: :user_tests
end
# rails g model question text:text
class Question < ApplicationRecord
belongs_to :test
end
# rails g model answer text:text
class Answer < ApplicationRecord
belongs_to :question
end
Then you want a class that "wraps" all the UserAnswers which can be though of as the actual sheet of paper that you fill out when you answer a test:
# rails g model user_test user:references test:references
class UserTest < ApplicationRecord
delegate :title, :description to: :test
belongs_to :user
belongs_to :test
has_many :user_answers
has_many :questions, through: :test
accepts_nested_attributes_for :user_answers
# creates a user answer for every question in the tests
def seed!
test.questions.each do |q|
user_answers.new(question: q)
end
end
end
class UserAnswer < ApplicationRecord
delegate :text, to: :question
belongs_to :user_test
belongs_to :question
belongs_to :answer
has_many :potential_answers, though: :question,
source: :answers
end
The way that nested attributes works is that when you pass an array of hashes to the special *_attributes= setter ActiveRecord will handle building multiple instances of the nested records at once:
#user_test = UserTest.new(
test_id: 1,
user_answer_attributes: [
{ question_id: 1, answer_id: 2 },
{ question_id: 2, answer_id: 5 },
{ question_id: 2, answer_id: 5 }
]
)
The RESTful way to model gathering responses to differents test is by creating nested routes:
# routes.rb
resources :tests do
resources :user_tests, only: [:new, :create]
end
class UserTestsController < ApplicationRecord
before_action :set_test
before_action :authenticate_user! # assuming you're using Devise
# GET /tests/:test_id/user_tests/new
def new
#user_test = #test.user_tests.new
#user_test.seed!
end
# POST /tests/:test_id/user_tests
def create
#user_test = #test.user_tests.new(user_test_params) do |ut|
ut.user = current_user
end
if #user_test.save
redirect_to '/somewhere', success: 'Thank you for your answers'
else
render :new
end
end
private
def set_test
#test = Test.find(params[:test_id])
end
def user_test_params
params.require(:user_test)
.permit(
user_answer_attributes: [:question_id, :answer_id]
)
end
end
In the form you use fields_for to loop through the assocation and create inputs for each nested record:
<%= form_with(model: #user_test) do |form| %>
<div class="questions">
<%= form.fields_for(:user_answers) do |user_answer| %>
<div class="question">
<%= user_answer.label :answer_id, user_answer.object.text %>
<%= user_answer.collection_select(
:answer_id,
user_answer.object.potential_answers,
:id,
:text
) %>
</fieldset>
<% end %>
<% end %>

ActiveRecord: how to build has_many relation with foreign key or null

I have these two models:
class Promotion < ActiveRecord::Base
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :promotions
end
And consider these 3 promotions:
--------------------------
| PROMOTIONS |
--------------------------
| id | name | product_id |
| 1 | sale | NULL |
| 2 | 10% | 1 |
| 3 | 20% | 2 |
--------------------------
When the product_id is NULL, the promotion applies to all products, so I'd like to be able to get all the promotions for a product as follows:
Product.find(1).promotions # => [1,2]
Product.find(2).promotions # => [1,3]
How can I achieve this?
You could go about finding promotions like this a few different ways. One way would be to just access the Promotion model directly
promotions1 = Promotion.where(product_id: [1, nil])
promotions2 = Promotion.where(product_id: [2, nil])
You could add this as a method in your Product model
class Product < ActiveRecord::Base
def all_promotions
Promotion.where(product_id: [self.id, nil])
end
end
And then use it as follows:
Product.find(1).all_promotions # => [1,2]
Another way could be to concat the promotions of Product object with all promotions that aren't attached to a specific product. This definitely isn't the best solution, since you can't really take advantage of ActiveRecord to order the promotions; you'd have to use array sorting.
def all_promotions
self.promotions + Promotion.where(product_id: nil)
end

How do I cycle through an object and it's nested objects before moving on to the next object in Rails?

I'm a beginner at RoR.
What I'm trying to accomplish is to cycle through a list of service_types (brakes, drums, pads, etc...) per service category (brakes) belonging to each tech before going on to the next service category (suspension).
I have a setup with the following:
Gems
devise: user signup
cocoon: customizable nested objects
My view from my services/show.html.erb
<tbody>
<% #services.each do |service| %>
<tr>
<td><%= service.name %></td>
<td><%= **??? Don't know how to cycle through to capture the tech's list `services` and `service types**` %>
</tr>
<% end %>
</tbody>
service.rb model
class Service < ActiveRecord::Base
has_many :service_types
belongs_to :tech
accepts_nested_attributes_for :service_types, :reject_if => :all_blank, :allow_destroy => true
end
techs_controller.rb
class TechsController < ApplicationController
def index
#techs = Tech.all
#services = Service.all
end
def show
#tech = Tech.find(params[:id])
#services = #tech.services.all
##service_types = #service.service_types.all
end
end
tech.rb model (db with list of techs - registered via devise gem)
class Tech < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :service_types
has_many :services
has_many :appointments
has_many :customers, :through => :appointments
end
Edited - 5/25 [Reason: copy paste error / typo]
Service (contains data with a list of services main categories)
mysql> describe services;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
| tech_id | int(11) | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
The tech_id references a record in the tech db
ServiceType (from the cocoon gem which allows me to create nested service types with several custom attributes)
mysql> describe service_types;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| description | varchar(255) | YES | | NULL | |
| time | varchar(255) | YES | | NULL | |
| price | decimal(8,2) | YES | | NULL | |
| decimal | decimal(8,2) | YES | | NULL | |
| service_id | int(11) | YES | MUL | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
The service_id references a record in the services db
After days of researching, the closest I've come to accomplishing what I wanted was this from my view:
<tbody>
<% #services.each do |service| %>
<tr>
<td><%= service.name %></td>
<td><%= service.service_types.all.collect{|s| [s.name, s.time, s.price} %></td>
</tr>
<% end %>
</tbody>
</table>
The above produces this:
Brakes [["Rotors", 1HR], ["Drums", 1HR]]
What I'm looking for this to output is the following:
Below is the output I'm looking for:
+---------+----------+-----+-------+
| Brakes | Type | Time | Price |
+---------+--------+-------+-------+
| Rotors | 1HR | 100 |
| Drums | 1HR | 100 |
+-------------+----------+-----+-------+
| Suspension | Type | Time | Price |
+-------------+--------+-------+-------+
| Struts | 2HR | 100 |
| Shocks | 1HR | 100 |
I would like to cycle through a service (brakes) and all of it's nested service_types before moving on to cycling through the next service category (suspension). The
Any help would be appreciated.
Thanks
<% #services.each do |service| %>
<tr>
<td><%= service.name %></td>
<td>
<table>
<tr>
<th>Name</th>
<th>Time</th>
<th>Price</th>
</tr>
<% service.service_types.each do |service_type| %>
<tr>
<td><%= service_type.name %></td>
<td><%= service_type.time %></td>
<td><%= service_type.price %></td>
</tr>
<%end%>
</table>
</td>
</tr>
<% end %>
And one more thing
service_type_id
is not required in Service table.

How do I show grouped rows from model?

I have a model:
class Row < ActiveRecord::Base
belongs_to :description
attr_accesible :id, :q_id, :quantity, description_id
end
class Description < ActiveRecord::Base
has_many :rows
translate :description #globalize3, ignore it.
attr_accesible :id, :other
end
In this case, Description and Row have a 1:N relationship (N from 1 to 3).
What is the best way to show this in table format in Haml? For example if we have in the DB:
Row:
1 | 1 | 20 | 1
2 | 2 | 22 | 1
3 | 1 | 30 | 2
4 | 3 | 31 | 2
5 | 2 | 32 | 2
Description:
1 | asd
2 | zxc
I would like to show this form:
desc | q1 | q2 | q3 |
-----+----+----+----+
asd | 20 | 22 | -- |
zxc | 30 | 32 | 31 |
UPDATE
I added a new attribute (q_id) that indicates the "q" position (1, 2, or 3) to my Row model.
In your controller code:
#descriptions = Description.eager_load(:rows).order('descriptions.id, rows.q_id')
In your view:
%table
%tr
%th= 'desc'
%th= 'q1'
%th= 'q2'
%th= 'q3'
- #descriptions.each do |description|
%tr
%td= description.description
- (0..2).each do |i|
%td= description.rows[i].try(:quantity) || '--'
UPDATE
If you want to limit the rows to a specific user association, you can do so like this:
#descriptions = Description.eager_load(:rows).
where(rows: {user_id: some_user.id}).
order('descriptions.id, rows.q_id')
If you are concerned that this may exclude a description that has no matching rows, you can substitute a SQL fragment with a NULL alternative:
#descriptions = Description.eager_load(:rows).
where('rows.user_id = ? OR rows.id IS NULL', some_user.id).
order('descriptions.id, rows.q_id')
Something like:
%table
%tr
- Description.includes(:rows).find_each do |description|
%td= description.description
%td= format_quantity description.rows.first
%td= format_quantity description.rows.second
%td= format_quantity description.rows.third
And a helper function
def format_quantity(row)
row.present? ? row.quantity : '--'
end
UPDATE
For the order of rows, you could specify that on your relation
class Description < ActiveRecord::Base
has_many :rows, order: 'q_id'

RoR: Check boxes in form_for for has_many, :through with a Join model

Once again, I have my form_for for my Order model, which has_many :services, :through => :requests. Here's the layout of how I have my relationships mapped:
__________ _________
| Customer | | Utility |
---------- ---------
|| ^ /\
|| | ||
\/ | /\
_______ _________ _________
| Order | <=====< | Request | >=====> | Service |
------- --------- ---------
\/
||
\/
_________
| Company |
---------
Where:
---> = belongs_to
===> = has_many
<==< join model >==> = has_many, :through
On my Order form, I want to have an array of checkboxes that represent the services available, such that even though the checkboxes are labelled by the Company and categorized by Utility, the Order ends up with the Service association when the order is complete (because that's really what the customer is ordering: a Company to provide a Utility, which is a Service).
How do I accomplish this in my form?
form view:
- form_for #order do |order_form|
-# order form inputs, etc.
- order_form.fields_for :customer do |customer_form|
-# customer form inputs
- order_form.fields_for :services do |services_form|
%dl
- #services.each do |service_name, services|
%dt= service_name
- services.each do |service_item|
%dd
=# check_box ??????????
=# label ??????????, Company.find(service_item.company_id).name
%p= order_form.submit 'Create Order'
Where:
#services = Service.all.to_set.classify { |service_item| Utility.find(service_item.utility_id).name }
There is a Railscast on HABTM checkboxes -- it's an oldie but goodie. I'm pretty sure it should still work even with a join model.

Resources