problems accessing variables in a loop within my view - ruby-on-rails

I'm fairly new to Ruby/Rails and I'm having a weird issue that I can't seem to understand what exactly I'm doing wrong.
I have the following code in my view
<% gameNum = 0 %>
gameNum: <%= gameNum %>
homeTeamIndex: <%= #games[gameNum].homeTeamIndex %>
awayTeamIndex: <%= #games[gameNum].awayTeamIndex %><br />
<%= #NflTeams[#games[gameNum].homeTeamIndex].name %>
<%= #NflTeams[#games[gameNum].awayTeamIndex].name %><br />
<% gameNum = 1 %>
gameNum: <%= gameNum %>
homeTeamIndex: <%= #games[gameNum].homeTeamIndex %>
awayTeamIndex: <%= #games[gameNum].awayTeamIndex %><br />
<%= #NflTeams[#games[gameNum].homeTeamIndex].name %>
<%= #NflTeams[#games[gameNum].awayTeamIndex].name %><br />
<% (0..#games.count).each do |gameNum| %>
gameNum: <%= gameNum %>
homeTeamIndex: <%#= #games[gameNum].homeTeamIndex %>
awayTeamIndex: <%#= #games[gameNum].awayTeamIndex %> <br />
<%#= #NflTeams[#games[gameNum].homeTeamIndex].name %>
<%#= #NflTeams[#games[gameNum].awayTeamIndex].name %>
<% end %>
And I get the following results when I view my view:
gameNum: 0 homeTeamIndex: 10 awayTeamIndex: 3
Detroit Lions Buffalo Bills
gameNum: 1 homeTeamIndex: awayTeamIndex:
Cinncinatti Bengals Cleveland Browns
gameNum: 0 homeTeamIndex: awayTeamIndex:
homeTeamIndex: awayTeamIndex:
gameNum: 1 homeTeamIndex: awayTeamIndex:
homeTeamIndex: awayTeamIndex:
gameNum: 2 homeTeamIndex: awayTeamIndex:
homeTeamIndex: awayTeamIndex:
gameNum: 3 homeTeamIndex: awayTeamIndex:
homeTeamIndex: awayTeamIndex:
gameNum: 4 homeTeamIndex: awayTeamIndex:
However, if I uncomment any of the lines:
homeTeamIndex: <%#= #games[gameNum].homeTeamIndex %>
awayTeamIndex: <%#= #games[gameNum].awayTeamIndex %> <br />
<%#= #NflTeams[#games[gameNum].homeTeamIndex].name %>
<%#= #NflTeams[#games[gameNum].awayTeamIndex].name %>
I get the following error(method name changes based on which line I uncomment):
undefined method `homeTeamIndex' for nil:NilClass
I really don't understand what is happening within the loop that makes the instance variables unavailable.
I'm hoping someone can tell me what the heck I'm doing wrong because this sure seems like a simple thing to do in a view and I can't get it to work.
UPDATE
I changed my view as follows, per Dmitry's advice:
<% gameNum = 0 %>
<% (#games).each do |game| %>
<strong>Game <%= gameNum+1 %> </strong> <br />
<%= image_tag(#NflTeams[game.homeTeamIndex].imagePath,
size: "40") %>
<%= #NflTeams[game.homeTeamIndex].name %>
VS
<%= image_tag(#NflTeams[game.awayTeamIndex].imagePath,
size: "40") %>
<%= #NflTeams[game.awayTeamIndex].name %> <br />
<% gameNum += 1 %>
<% end %>
And the Models are as follows:
NflTeams
class CreateNflTeams < ActiveRecord::Migration
def change
create_table :nfl_teams do |t|
t.string :name
t.string :imagePath
t.timestamps
end
end
end
Game
class CreateGames < ActiveRecord::Migration
def change
create_table :games do |t|
t.integer :homeTeamIndex
t.integer :awayTeamIndex
t.integer :spread
t.integer :week_id
t.timestamps
end
end
end
The awayTeamIndex and homTeamIndex are an index into the NflTeams model so I can easily pull out the Name and ImagePath.
I am still getting the undefined method on the line:
<%= image_tag(#NflTeams[game.homeTeamIndex].imagePath,
size: "40") %>
Any other suggestions?
UPDATE 2
I only included the migrations because there isn't much in the models for these two models.
But here it is.
class NflTeam < ActiveRecord::Base
end
class Game < ActiveRecord::Base
belongs_to :week
validates :homeTeamIndex, :inclusion => { :in => 0..100 }
validates :awayTeamIndex, :inclusion => { :in => 0..100 }
end
And here is the controller code:
class WeeksController < ApplicationController
before_action :signed_in_user
before_action :confirmed_user
...
def show
#week = Week.find(params[:id])
#games = #week.games
#NflTeams = NflTeam.all
end
...
end

You are not accessing you #games in a loop correctly. It looks like you are thinking about gameNum in this line:
<% (0..#games.count).each do |gameNum| %>
as if it is a numerical index and using it to access an object in a collection, in the following line:
homeTeamIndex: <%#= #games[gameNum].homeTeamIndex %>
This is wrong because "gameNum" is actually your object from a collection of #games fetched for you by magic ruby, so what you need is something like this:
<% (#games).each do |game| %>
gameNum: <%= game.number %>
homeTeamIndex: <%= game.homeTeamIndex %>
awayTeamIndex: <%= game.awayTeamIndex %> <br />
<%= game.homeTeam.name %>
<%= game.awayTeam.name %>
<% end %>
Hope this helps :)
P.S.
This solution depends on how your models are set up of course, if you can show those it might make your problem clearer for others to explain.
EDIT - Complete solution
I will just show you the way to present a peace of info as in your solution.
To start off a small advise, you mentioned in your comment
changed it to just query the database in the form for both the
homeTeamIndex and the awayTeamIndex
While this might work it is a bad practice to place such routines in the view, it is better to define a method to complete such routine in the helper and call it in the view.
Now, to the solution...
First you need to properly define model associations, which are only partially defined judging from the code you have shared. What you need is:
Tell all classes that they are related (which they are) like this:
I would loose 'Nfl' in team since it makes naming of classes confusing
class Week < ActiveRecord::Base
has_many :games
end
class Game < ActiveRecord::Base
belongs_to :week
belongs_to :home_team, class_name: "Team", foreign_key: :home_team_id
belongs_to :away_team, class_name: "Team", foreign_key: :away_team_id
end
class Team < ActiveRecord::Base
has_many :home_games, class_name: "Game", foreign_key: :home_team_id
has_many :away_games, class_name: "Game", foreign_key: :away_team_id
end
Note: Have a look at Active Record Associations for a more in-depth understanding of those.
EDIT
Corrected model associations above and tested to make sure those are working:)
Make sure you have the following database schema to back up the associations...
create_table "games", force: true do |t|
t.integer "game_number"
t.integer "week_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "home_team_id"
t.integer "away_team_id"
end
create_table "teams", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "weeks", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
EDIT: mind the fact that all tables do include id field by default when you create models in rails
With these associations in place you can now get the week in your controller as you did before:
class WeeksController < ApplicationController
...
def show
#week = Week.find(params[:id])
#games = #week.games
end
...
end
And finally construct your view:
In WeeksController#show
/views/weeks/show.html.erb
<% if #games.any? %>
<% render #games %>
<% end %>
This line "render #games' will search for '/views/games/_game.html.erb' and render it for every game in the collection #games
*/views/games/_game.html.erb - partial view*
<strong> Game <%= game.game_number %> </strong> <br />
<%= image_tag(game.home_team.imagePath, size: "40") %>
<%= game.home_team.name %>
VS
<%= image_tag(game.away_team.imagePath, size: "40") %>
<%= game.away_team.name %> <br />
And that is it, the only fiddly bit is getting the model associations wright which is not too difficult.

Related

Create objects based on received data from params in Ruby on Rails

Description
I am trying to create messages based on selected (via check box) users from the browser in Ruby on Rails.
Snapshot:
Steps to reproduce
My schema
ActiveRecord::Schema.define(version: 2021_11_13_142255) do
create_table "messages", force: :cascade do |t|
t.text "content"
t.integer "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "role"
t.integer "phone"
t.boolean "admin"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
end
messages_controller.rb
class MessagesController < ApplicationController
def new
#users = User.all
#message = Message.new(message_params)
end
def create
params[:user_objs].each do |u|
# "params.inspect" returns
# {"authenticity_token"=>"[FILTERED]",
# "user_objs"=>
# ["{\"id\":1,\"name\":\"Alex\",\"role\":\"Engineer\",\"phone\":998943333303,\"admin\":true,\"created_at\":\"2021-11-13T14:37:54.962Z\",\"updated_at\":\"2021-11-13T14:37:54.962Z\"}",
# "{\"id\":2,\"name\":\"Lucy\",\"role\":\"Accountant\",\"phone\":998943333303,\"admin\":false,\"created_at\":\"2021-11-13T14:39:52.742Z\",\"updated_at\":\"2021-11-13T14:39:52.742Z\"}"],
# "message"=>{"content"=>"Message from the browser"},
# "commit"=>"Send"}
person = JSON.parse(u)
#message = person.messages.new(message_params)
if #message.save
redirect_to root_path
else
#users = User.all
render :new
end
end
end
private
def message_params
params.permit(
:content,
:user_id
)
end
end
messages => new.html.erb
<div>
<h1>Create and send a new message!</h1>
<%= form_for(#message) do |form| %>
<% if #message.errors.any? %>
<div class="alert alert-danger">
<h5 class="fw-bold">Invalid input!</h5>
<%= #message.errors.full_messages.each do |error| %>
<div><%= error %></div>
<% end %>
</div>
<% end %>
<% #users.each do |u| %>
<div>
<p><%= check_box_tag "user_objs[]", u.to_json %> <%= u.name %></p>
</div>
<% end %>
<p class="mb-3">
<%= form.label :content, class: "form-label" %>
<%= form.text_field :content, class: "form-control", autofocus: true, placeholder: "John_D" %>
</p>
<p class="mb-3">
<%= form.submit "Send", class: "btn btn-primary" %>
</p>
<% end %>
</div>
<%= params.inspect %>
Models
# user.rb
class User < ApplicationRecord
has_many :messages
end
# message.rb
class Message < ApplicationRecord
belongs_to :user
end
Expected behavior
I was expecting the creation of messages for all selected users
Actual behavior
NoMethodError in MessagesController#create
undefined method `messages' for #<Hash:0x000000011fe2b420>
I tried different ways, but can't convert Ruby objects to JSON in my params user_objs[] so that I can parse it in my controller to create messages based on those selected users in the user_objs[] params.
Environment info
ruby -v
ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [arm64-darwin20]
rails -v
Rails 6.1.4.1
Thanks for any given help 🙏
If you want to create a system where you send a single message to multiple users you would setup a join table:
class User < ApplicationRecord
has_many :user_messages
has_many :recieved_messages, though: :user_messages,
source: :message,
inverse_of: :recipients
end
# rails g model user_message user:belongs_to message:belongs_to read:boolean
class UserMessage < ApplicationRecord
belongs_to :user
belongs_to :message
# make sure to add a compound unique index to the migration as well
validates_uniqueness_of :user_id, scope: :message_id
delegate :content, to: :message
end
class Message < ApplicationRecord
has_many :user_messages
has_many :recipients, though: :user_messages,
source: :user,
inverse_of: :recieved_messages
end
has_many :recipients will create a recipient_ids= setter and a recipient_ids getter that you can use in your form:
<div>
<h1>Create and send a new message!</h1>
<%= form_with(model: #message) do |form| %>
<% if #message.errors.any? %>
<div class="alert alert-danger">
<h5 class="fw-bold">Invalid input!</h5>
<%= #message.errors.full_messages.each do |error| %>
<div><%= error %></div>
<% end %>
</div>
<% end %>
<p class="mb-3">
<%= form.collection_checkboxes(:recipient_ids, #users, :id, :name) %>
</p>
<p class="mb-3">
<%= form.label :content, class: "form-label" %>
<%= form.text_field :content, class: "form-control", autofocus: true, placeholder: "John_D" %>
</p>
<p class="mb-3">
<%= form.submit "Send", class: "btn btn-primary" %>
</p>
<% end %>
</div>
There is absolutely no need to pass the entire record as JSON - you just pass an array of IDs and rails will do all the work of creating the join table rows for you:
class MessagesController < ApplicationController
def new
#users = User.all
#message = Message.new
end
def create
#message = Message.new(message_params)
if #message.save
redirect_to root_path
else
#users = User.all
render :new
end
end
private
def message_params
params.require(:message)
.permit(
:content,
recipient_ids: []
)
end
end
This avoids the complexity of creating multiple records from a single request and the whole conundrum that you're binding the form to a single instance of Message but creating a bunch of records which is bound to lead to confusion.
If you want to create multiple records at once it can be done but the complexity is far higher and you have to deal with stuff like how to handle errors if creating one message fails and this might be beyond your current skill level.
The issue is that you are assigning a json object/hash in person = JSON.parse(u). This is not an active record so when doing person.messages it throws the error. I believe what you need in the create action is something like:
user = JSON.parse(u)
# make sure user.inspect gives you the user object you want
person = User.find(user["id"])
# person.inspect should give you the active record for the user

Rails create multiple rows from one form

I have a rails app that has building and floors models
class Building < ApplicationRecord
has_many :floors
end
class Floor < ApplicationRecord
belongs_to :building
end
In my building form I want to ask the user how many floors the building has, and then when the building is created I want to add that many floors.
So the form would look like so:
<%= form_with(model: building, local: true) do |form| %>
<% if building.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(building.errors.count, "error") %> prohibited this building from being saved:</h2>
<ul>
<% building.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div class="field">
<%= form.label :" How many floors does the building have" %>
<%= form.number :floors %> * not sure how to do this
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
Note I don't want the number of floors saved on the building model, it just creates the number of floors the user specified.
So if I created a building called "Walts Place" and said it has 10 floors it would create: Walts Place with id:1, and 10 floors with the building_id of 1.
Does that make sense?
Your help is greatly appreciated.
Update:
ActiveRecord::Schema.define(version: 2019_07_30_093037) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "buildings", force: :cascade do |t|
t.string "name"
t.float "distancetocondensors"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "floors", force: :cascade do |t|
t.bigint "building_id", null: false
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["building_id"], name: "index_floors_on_building_id"
end
add_foreign_key "floors", "buildings"
end
you can modify it in your building controller in create action.
def create
#building = Building.new(building_params)
if #building.save
floors = params[:number].to_i
floors.times do
Floor.create(building: #building)
end
redirect_to building_path
else
redirect_to error
end
end
in your form add a field for number of floor without erb
<%= form_with(model: building, local: true) do |form| %>
<% if building.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(building.errors.count, "error") %> prohibited this building
from being saved:</h2>
<ul>
<% building.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div class="field">
<label> How many floors does the building have</label>
<input type="number" name="number"/>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
You can use nested attributes to help you here.
First, in your Building model add:
has_many :floors
accepts_nested_attributes_for :floors
Then in your BuildingController's create action do:
class BuildingController < ApplicationController
...
def create
floors_amount = params[:building][:floors] || 0
building_params = params.require(:building).permit(:name).merge({
# just fill floors array with empty hashes since `id` will be added automatically
floors_attributes: (1..floors_amount).to_a.fill({})
})
#building = Building.create(building_params)
if #buildnig.errors
render :new # show the form page with an error
else
redirect_to #building # or whenever you want to redirect
end
end
...
end
The advantage here is that it wraps the creation of building and floors into a transaction so if something goes wrong it will rollback all changes. I.e. there won't be a case where new Building is inserted into DB but floors didn't due to some error during their creation.
Another convenience here is that if some validation error appears either on building or on any of the floor then it will be set into building.errors. Means you can easily do render :new in the case of errors to display them.
Note:
I see that in your form view you access Building instance as building. So I'm not really sure how you pass it to this view.
In my example I saved building into #building variable so that in this view form you will have to access it as #building. I believe in your new action in BuildingController you should set it to #building as well:
class BuildingController < ApplicationController
...
def new
#building = Building.new
end
...
end
And in your view you will access it as #building then, not building.
Hope, that makes sense

Rails- How can I create view for nasted tables

I want to get data like this in the show.html.erb , but it doesn't work.
How can I get data from spot table?
here is the code.
show.html.erb
<% #planaction.each do |action| %>
<hr>
<%= action.spot.name %>
<%= action.spot.description %>
<hr>
<%= action.title %>
<%= action.experience %>
<% end %>
plan.rb
class Plan < ActiveRecord::Base
has_many :plan_actions
end
plan_action.rb
class PlanAction < ActiveRecord::Base
belongs_to :plan
has_one :spot
end
spot.rb
class Spot < ActiveRecord::Base
belongs_to :plan_action
end
plan_actions_controller.erb
class PlanPagesController < ApplicationController
def show
#plan = Plan.find(params[:id])
#planaction = #plan.plan_actions
end
end
and error message here
undefined method `name' for nil:NilClass
and here is migration file for spot table.
class CreateSpots < ActiveRecord::Migration
def change
create_table :spots do |t|
t.integer :spot_id
t.integer :plan_action_id
t.string :name
t.text :description
t.time :time_open
t.time :time_close
t.date :dayoff
t.string :address
t.integer :tel
t.string :image
t.string :image2
t.string :image3
t.timestamps null: false
end
end
end
Looks good to me.
The issue is probably (can't be certain without seeing your logs) that the plan_action doesn't have an associated spot record.
To fix this, you should use some conditional logic:
<% #planaction.each do |action| %>
<hr>
<% if action.spot %>
<%= action.spot.name %>
<%= action.spot.description %>
<hr>
<% end %>
<%= action.title %>
<%= action.experience %>
<% end %>
Again, this is speculation. I wrote the answer because I felt it best to provide some sort of idea as to how to resolve it. The above should work.
I also think as Rich Peck that you don't have a record in spots table with plan_action_id corresponding to a plan action.
Following rails convention, I suggest the following:
class PlanAction < ActiveRecord::Base
belongs_to :plan
has_one :spot
delegate :name, :description, to: :spot, prefix: true, allow_nil: true
end
and in your view:
<%= action.spot_name %>
<%= action.spot_description %>
Finally, get your validations corrected. For example, if a plan_action should have a spot, then you need to use nested forms for both spot and plan action.

Create a Dynamic Table: Attributes = Columns, Nested Attributes = Rows

Rows & columns, baby. Rows & columns.
Within one table:
When a User adds a new :name & :metric I want it to create a new column.
When a User adds a new :result_value & :date_value I want it to create a new row.
This is what my table currently looks like:
For the life of me I can't figure out why something that seems so elementary is so difficult. I don't know if I'm suppose to create the rows and columns in the database, model, helper or index view and/or if I should be using some other language or gem to help me. Any direction would be greatly appreciated because a dozen google searches hasn't done it for me.
Do I use add_column in the database or something?
class CreateQuantifieds < ActiveRecord::Migration
def change
create_table :quantifieds do |t|
t.string :categories
t.string :name
t.string :metric
t.timestamps null: false
end
end
end
Or do I add columns via the model?
class Quantified < ActiveRecord::Base
belongs_to :user
has_many :results #correct
accepts_nested_attributes_for :results, :reject_if => :all_blank, :allow_destroy => true #correct
scope :averaged, -> { where(categories: 'averaged') }
scope :instance, -> { where(categories: 'instance') }
CATEGORIES = ['averaged', 'instance']
end
class Result < ActiveRecord::Base
belongs_to :user
belongs_to :quantified
end
Or do I keep playing around with the index view I've tried all sorts of 's and html tags, but I could never get the code to look like above, which is why I think the answer may lie deeper.
<!-- Default bootstrap panel contents -->
<div id="values" class="panel panel-default">
<div class="panel-heading"><h4><b>AVERAGE</b></h4></div>
<table>
<% #averaged_quantifieds.each do |averaged| %>
<% if averaged.user == current_user %>
<th><%= averaged.name %> (<%= averaged.metric %>)</th>
<td><% averaged.results.each do |result| %>
<tr><td><%= result.date_value.strftime("%m-%Y") %></td>
<td><%= result.result_value %></td></tr>
<% end %></td>
<% end %>
<% end %>
</table>
</div>
<br>
<br>
<br>
<br>
<!-- Default bootstrap panel contents -->
<div id="values" class="panel panel-default">
<div class="panel-heading"><h4><b>INSTANCE</b></h4></div>
<table>
<% #instance_quantifieds.each do |instance| %>
<% if instance.user == current_user %>
<th><%= instance.name %> (<%= instance.metric %>)</th>
<td><% instance.results.each do |instance| %>
<tr><td><%= result.date_value.strftime("%m-%Y") %></td>
<td><%= result.result_value %></td></tr>
<% end %></td>
<% end %>
<% end %>
</table>
</div>
<div class="values-button">
<%= link_to new_quantified_path, class: 'btn' do %>
<b><span class="glyphicon glyphicon-plus"</span></b>
<% end %>
</div>
I think you may have a misunderstanding of how the database should be used.
I don't think you would ever want the actual database tables to be dynamic. Instead, you should set up two tables. One for the User, and one for the Metrics. Then a user can add new metrics.
# == Schema Information
# Table name: users
# id :integer
Class User < ActiveRecord::Base
has_many :metrics
end
# == Schema Information
# Table name: metrics
# id :integer
# type :string
# result :string
# user_id :integer
Class Metric < ActiveRecord::Base
belongs_to :user
end
This way, you can create as many types of metrics as you want. And you can determine who the metric belongs to by the :user_id field.
Reading this from head to toe should help: http://guides.rubyonrails.org/association_basics.html

Submit multiple model objects from one form with Rails

I've tried following this advice but I haven't succeeded yet in generating a form containing 3 objects of the same type under one submit button.
When I navigate to a page that should show a form containing fields for 3 objects (called elements in this example) I get the following error:
undefined method 'elements' for nil:NilClass
Any pointers would be much appreciated! My code is as follows:
app/controllers/elements_controller.rb
class ElementsController < ApplicationController
def index
#element_group = ElementGroup.new
render 'pages/index'
end
end
app/views/pages/home.html.erb
<%= render 'element_groups/form'%>
app/views/element_groups/_form.html.erb
<% form_for :element_group do |f|%>
## The error comes from this next line, as f.object is nil
<% f.object.elements.each do |element| %>
<% f.fields_for element do |element_form| %>
<%= element_form.text_field :content %>
<%= element_form.text_field :element_type %>
<%= element_form.text_field :subtype %>
<% end %>
<% end %>
<% end %>
app/models/element_group.rb
class ElementGroup
attr_accessor :elements
def elements
#elements = []
3.times do
#elements << Element.new
end
#elements
end
end
app/models/element.rb
class Element < ActiveRecord::Base
attr_accessible :element_type, :subtype, :content
end
db/schema.rb
create_table "elements", :force => true do |t|
t.string "element_type"
t.string "subtype"
t.string "content"
t.datetime "created_at"
t.datetime "updated_at"
end
Have you tried to change to <% form_for #element_group do |f|%> ?

Resources