Rails4: How to pass dynamic array from view to controller - ruby-on-rails

I have a problem creating Vote which contains value of points that user decided to give for a candidate (Borda Count Method). Values are in number_fields and I want them to be saved in Vote objects.
In my case I am only able to get value from the last number_field and I have no idea how to fix it. Other values are being overwritten so I came up with idea to create an array of points which contains values taken from many number_field_tag and pass them from view to controller.
Something about survey structure:
There is a Surv (survey) which contains many Polls (questions).
One Poll contains many Vote Options.
There are also Votes which save votes of user.
OUTPUT from params
{"utf8"=>"✓", "poll"=>{"id"=>"11"}, "9"=>"17", "arr"=>"1212", "pnts"=>"", "10"=>"20", "11"=>"22", "commit"=>"Zagłosuj", "controller"=>"votes", "action"=>"create"}
VIEW:
show.html.erb
<p id="notice"><%= notice %></p>
<h2>Twoja ankieta:</h2>
<h1>
<%= #surv.topic %>
</h1>
<h2>
<%= election_type %>
</h2>
<%= render 'voting_form' %>
<%= link_to 'Edit', edit_surv_path(#surv) %> |
_voting_form.html.erb
<%= form_tag votes_path, method: :post, remote: true, id: 'voting_form' do %>
<% if #surv.votingtype == "default" %>
<%# here is unnecessary code ... %>
<% elsif #surv.votingtype == "bordy" %> <%# BORDY %>
<% #polls.each do |poll| %>
<h2><%= poll.topic %></h2>
<h2>POLL ID: <%= poll.id %></h2>
<%= hidden_field_tag 'poll[id]', poll.id %>
<%= arr = [] %>
<%= i = 0 %>
<% poll.vote_options.each do |option| %>
<div class="form-group">
<%= content_tag(:label) do %>
<div class="select">
<%= option.title %>
<%= radio_button_tag option.poll_id, option.id %>
<%# if selected then vote is created %>
<%= number_field_tag :point %>
<%#= arr << :point %>
<%= hidden_field_tag 'pnts', arr %>
<%# some tries to pass params %>
</div>
<% end %>
<%= visualize_votes_for option %>
</div>
<% end %>
<p><b>Głosów razem: <%= poll.votes_summary %></b></p>
<% end %>
<% if current_user && current_user.voted_for3?(#surv) %>
<p>Twój głos został DO ANKIETY został zapisany.</p>
<% else %>
<%= submit_tag 'Zagłosuj', class: 'btn btn-lg btn-primary' %>
<% end %>
<% end %>
<% end %>
CONTROLLER:
votes_controller.rb
class VotesController < ApplicationController
def create
#poll = Poll.find_by_id(params[:poll][:id])
#surv = Surv.find_by_id(#poll.surv_id)
puts "Surv id:"
puts #surv.id
#surv.polls.each do |p|
puts "Poll ID (poll voted)."
puts p.id
puts "Vote option ID (vote option voted)"
puts params[p.id.to_s]
#option = p.vote_options.find_by_id(params[p.id.to_s])
if p && !current_user.voted_for?(p)
#here i would like to grab params specially specified for each poll
#vote = #option.votes.create({user_id: current_user.id, points: params[:pnts]})
##vote = #option.votes.create({user_id: current_user.id, points: params[:point]})
puts 'POINTS'
puts params.inspect
else
render js: 'alert(\'Glosowales juz w tej ankiecie!\');'
return
end
# end
end
#surv.update_attribute(:actual_votes, #surv.actual_votes+1)
if (#surv.maximum_votes > 0 && #surv.actual_votes >= #surv.maximum_votes)
respond_to do |format|
format.html { redirect_to terminate_surv_path(#surv), notice: 'Ostatni głos został oddany' }
format.js {render inline: "location.reload();" }
end
end
end
def vote_params
params.require(:vote).permit(:points)
end
end
MODEL:
vote_option.rb
class VoteOption < ActiveRecord::Base
belongs_to :surv
accepts_nested_attributes_for :surv
belongs_to :poll
accepts_nested_attributes_for :poll
has_many :votes, dependent: :destroy
has_many :users, through: :votes
validates :title, presence: true
end
vote.rb
class Vote < ActiveRecord::Base
belongs_to :user
belongs_to :vote_option
end
def surv_params
params.require(:surv).permit(:topic, :votingtype,
polls_attributes: [:id, :topic, :endtime, :endontime, :_destroy,
vote_options_attributes: [:id, :title, :_destroy]
])
end
Please let me know if you need some additional code or informations to help me fix my problem. Thank you in advance

Related

Rails 5 - has_many through: and nested fields_for in forms

I am new to RoR (using rails 5) and have problems with a has_many through: association.
I want to create Categories with different labels for different Languages.
Here is my model:
class Language < ApplicationRecord
has_many :category_infos
has_many :categories, through: :category_infos
end
class Category < ApplicationRecord
has_many :category_infos
has_many :languages, through: :category_infos
accepts_nested_attributes_for :category_infos
end
class CategoryInfo < ApplicationRecord
belongs_to :language
belongs_to :category
accepts_nested_attributes_for :language
end
The controller:
class CategoriesController < ApplicationController
def new
#category = Category.new
#languages = Language.all
#languages.each do |language|
#category.category_infos.new(language:language)
end
end
def create
#category = Category.new(category_params)
if #category.save
redirect_to #category
else
render 'new'
end
end
private
def category_params
params.require(:category).permit(:name, category_infos_attributes:[:label, language_attributes: [:id, :language]])
end
end
The form:
<%= form_with model: #category, local: true do |form| %>
<% if #category.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#category.errors.count, "error") %>:
</h2>
<ul>
<% #category.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<br/>
<% end %>
<p>
<%= form.label :name %>
<%= form.text_field :name %>
</p>
<p>
Labels:
</p>
<table>
<% #category.category_infos.each do |category_info| %>
<tr>
<td>
<%= category_info.language.name %>
</td>
<td>
<%= form.fields_for :category_infos, category_info do |category_info_form| %>
<%= category_info_form.fields_for :language, category_info.language do |language_form| %>
<%= language_form.hidden_field :id, value: category_info.language.id %>
<%= language_form.hidden_field :name, value: category_info.language.name %>
<% end %>
<%= category_info_form.text_field :label %>
<% end %>
</td>
</tr>
<% end %>
</table>
<p>
<%= form.submit %>
</p>
<% end %>
When I create a new category, I get this error :
Couldn't find Language with ID=1 for CategoryInfo with ID=
on Line :
#category = Category.new(category_params)
However I already have registered several languages in database (1 = English, 2 = French etc...)
How do I need to write the form so that I can create a Category and its CategoryInfos in English, French etc... at the same time?
Thanks in advance for your answers
You're making a classic newbie misstake and using fields_for when you just want to create an association by passing an id.
<%= form_with model: #category, local: true do |f| %>
# ...
<%= f.fields_for :category_infos do |cif| %>
<%= cif.collection_select(:language_id, Language.all, :name, :id) %>
<%= cif.text_field :label %>
<% end %>
<% end %>
While you could also pass the attributes to let a users create languages at the same time its very much an anti-pattern as it adds a crazy amount of responsibilities to a single controller. It will also create an authorization problem if the user is allowed to create categories but not languages.
Use ajax to send requests to a seperate languages controller instead if you need the feature.

Given the index value how can I update only those values in a Rails controller?

I have a nested form as detailed below with a checkbox which I'm hoping to use to note only the records I want to update. I've checked the html and I'm registering the correct index for each record with it. The html for both an example attribute and the checkbox are as follows:
<input id="game_game_rosters_attributes_0_game_id" name="game[game_rosters_attributes][0][game_id]" type="hidden" value="127">
<input id="add_0" name="add[0]" type="checkbox" value="false">
Now, I think I'm supposed to figure out which game_roster_attributes I want to update by checking to see if the checkbox with the same index has a value of "true". But I'm unsure how to do this in my games_controller.rb, because it's currently set to mass assignment of my attr_accessible variables with #game.update_attributes()...
games_controller.rb
def update
#game = Game.find(params[:id])
respond_to do |format|
if #game.update_attributes(params[:game])
format.html { redirect_to #game, notice: 'Game was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit"}
format.json { render json: #game.errors, status: :unprocessable_entry }
end
end
end
Nested Form
<%= form_for #game do |f| %>
# #game fields for editing here...
<% roster_options.each.with_index do |option, index| %>
<%= f.fields_for :game_rosters, option do |fields| %>
<%= fields.object.player.full_name %>
<%= fields.hidden_field :player_id %>
<%= fields.hidden_field :game_id %>
<%= check_box_tag 'add[' + index.to_s + ']', false %>
<% end %>
<% end %>
<% end %>
Models
game.rb
class Game < ActiveRecord::Base
attr_accessible :date, :game_rosters_attributes
has_many :game_rosters
has_many :players, :through => :game_rosters
accepts_nested_attributes_for :game_rosters, allow_destroy: true
end
game_roster.rb
class GameRoster < ActiveRecord::Base
attr_accessible :active, :winner, :player_id, :game_id, :placement
belongs_to :game
belongs_to :player
end
game_controller.rb
def new
#game = Game.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #game }
end
end
def edit
#game = Game.find(params[:id])
#game_roster = GameRoster.joins(:player).where(game_id: params[:id]).order('winner DESC, placement DESC')
#options = SeasonRoster.where("season_id = (?) AND NOT EXISTS ( SELECT * FROM game_rosters INNER JOIN games ON games.id = game_rosters.game_id WHERE game_rosters.player_id = season_rosters.player_id AND games.id = (?))", #game.season_id.to_s, #game.id.to_s)
#roster_options = Array.new
#options.each do |o|
c = GameRoster.new
c.player_id = o.player_id
c.challenge_id = params[:id]
c.winner = false
#roster_options.push(c)
end
end
I am not sure about your requirement. I assume that you want to save only those rosters with their 'add' field checked and remove the rest of them.
roster.rb
class Roster < ActiveRecord::Base
attr_accessor :add
end
game.rb
class Game < ActiveRecord::Base
before_save :mark_rosters_for_removal
def mark_rosters_for_removal
rosters.each do |roster|
unless roster.add
roster.mark_for_destruction
end
end
end
end
Nested Form
<%= form_for #game do |f| %>
# #game fields for editing here...
<% roster_options.each.with_index do |option, index| %>
<%= f.fields_for :game_rosters, option do |fields| %>
<%= fields.object.player.full_name %>
<%= fields.hidden_field :player_id %>
<%= fields.hidden_field :game_id %>
<%= fields.check_box :add, false %>
<% end %>
<% end %>
<% end %>
I would use a before_save and use the params hash to check to see if the check-box is checked or not. Then use the .delete method to delete that part of the params hash. Then #game.update_attributes() would only update the rosters that are still in the hash (whom's check-boxes are checked).
Nested Form
<%= form_for #game do |f| %>
# #game fields for editing here...
<% roster_options.each.with_index do |option, index| %>
<%= f.fields_for :game_rosters, option do |fields| %>
<%= fields.object.player.full_name %>
<%= fields.hidden_field :player_id %>
<%= fields.hidden_field :game_id %>
<%= check_box_tag :add, false %> #Notice change!!!!!
<% end %>
<% end %>
<% end %>
game.rb
class Game < ActiveRecord::Base
before_save :check_for_deletion
...
def check_for_deletion
index = 0
roster = params[:game][:game_rosters_attributes]
while !roster["#{index.to_s}"].nil?
if roster["#{index.to_s}"][:add] == "true" # Or "checked" or whatever. I forget the actual value
roster.delete "#{index.to_s}"
end
index += 1
end
end
end
You might have to play around with this a bit depending on how your params hash looks like. I did my best to figure it out. I'm also not 100% that "#{index.to_s}" will work (or it could even be overkill). But this should give you a good head start.
It seems like you are adding all players to a game and then deleting them on update if "add" is not checked.
If this is the case, then you can add a checkbox for _destroy and it will be handled for you, as you have added the "allow_destroy: true" flag on game_rosters in game.rb.
Note that it will now be a "remove" checkbox rather than an "add" checkbox.
Nested Form:
<%= form_for #game do |f| %>
# #game fields for editing here...
<% roster_options.each.with_index do |option, index| %>
<%= f.fields_for :game_rosters, option do |fields| %>
<%= fields.object.player.full_name %>
<%= fields.hidden_field :player_id %>
<%= fields.hidden_field :game_id %>
<%= fields.check_box :_destroy, false %>
<% end %>
<% end %>
<% end %>
From the Rails guide on nested forms:
"Don't forget to update the whitelisted params in your controller to also include the _destroy field"
http://guides.rubyonrails.org/form_helpers.html#nested-forms
So after taking some time away from the problem and coming back to it I found a simple enough solution. I ended up just reversing the :_destroy checkbox that I had been using in the mean time.
<%= fields.check_box :_destroy, {}, '0', '1' %>
Basically, I just reversed the value of what the check box would usually represent. You can see where I edited the nested form to use this method below.
Nested Form
<%= form_for #game do |f| %>
# #game fields for editing here...
<% roster_options.each.with_index do |option, index| %>
<%= f.fields_for :game_rosters, option do |fields| %>
<%= fields.object.player.full_name %>
<%= fields.hidden_field :player_id %>
<%= fields.hidden_field :game_id %>
<%= fields.check_box :_destroy, {}, '0', '1' %>
<% end %>
<% end %>
<% end %>

unable to create or update a belongs_to record in Rails 4

I'm new to Rails and struggling to get my belongs_to association right. I have an app where a painting belongs to an artist and an artist can have_many paintings. I can create and edit my paintings, however I can not edit or create artists except through the console. Through much Googling I feel I have got myself turned around. Any help would be much appreciated!
Here's my routes.rb file:
MuseumApp::Application.routes.draw do
resources :paintings
resources :paintings do
resources :artists
resources :museums
end
root 'paintings#index'
end
Here's my paintings Controller
def show
#painting = Painting.find params[:id]
end
def new
#painting = Painting.new
##artist = Artist.new
end
def create
safe_painting_params = params.require(:painting).permit(:title, :image)
#painting = Painting.new safe_painting_params
if #painting.save
redirect_to #painting
else
render :new
end
end
def destroy
#painting = Painting.find(params[:id])
#painting.destroy
redirect_to action: :index
end
def edit
#painting = Painting.find(params[:id])
end
def update
#painting = Painting.find(params[:id])
if #painting.update_attributes(params[:painting].permit(:title, :image)) #safe_params
redirect_to #painting
else
render :edit
end
end
Here's the form in my paintings view:
<%= form_for(#painting) do |f| %>
<fieldset>
<legend>painting</legend>
<div>
<%= f.label :title %>
<%= f.text_field :title %>
</div>
<div>
<%= f.label :image %>
<%= f.text_field :image %>
</div>
<%= form_for([#painting,#painting.create_artist]) do |f| %>
<div>
<%= f.label :Artist %>
<%= f.text_field :name %>
</div>
</fieldset>
<%= f.submit %>
<% end %>
<% end %>
Artists Controller:
class ArtistsController < ApplicationController
def index
#artists = Artist.all
#artists = params[:q] ? Artist.search_for(params[:q]) : Artist.all
end
def show
#artist = Artist.find params[:id]
end
def new
#artist = Artist.new
end
def create
#painting = Painting.find(params[:painting_id])
#artist = #painting.create_artist(artist_params)
redirect_to painting_path(#painting)
end
def destroy
#artist = Artist.find(params[:id])
#Artist.destroy
redirect_to action: :index
end
def edit
#artist = Artist.find(params[:id])
end
def update
#painting = Painting.find(params[:painting_id])
#artist = #artist.update_attributes(artist_params)
redirect_to painting_path(#painting)
end
end
private
def artist_params
params.require(:artist).permit(:name)
end
Index view:
<h1> Hello and Welcome to Museum App</h1>
<h3><%= link_to "+ Add To Your Collection", new_painting_artist_path %></h3>
<%= form_tag '/', method: :get do %>
<%= search_field_tag :q, params[:q] %>
<%= submit_tag "Search" %>
<% end %>
<br>
<div id="paintings">
<ul>
<% #paintings.each do |painting| %>
<li><%= link_to painting.title, {action: :show, id:painting.id} %> by <%= painting.artist_name %></li>
<div id = "img">
<br><%= link_to (image_tag painting.image), painting.image %><br>
</div>
<%= link_to "Edit", edit_painting_path(id: painting.id) %>
||
<%= link_to 'Destroy', {action: :destroy, id: painting.id},method: :delete, data: {confirm: 'Are you sure?'} %>
<% end %>
</ul>
</div>
In your case you should use accepts_nested_attributes_for and fields_for to achieve this.
Artist
has_many :paintings, :dependent => :destroy
accepts_nested_attributes_for :paintings
Painting
belongs_to :artist
And also you should try creating artist with paintings like this
form_for(#artist) do |f| %>
<fieldset>
<legend>Artist</legend>
<%= f.label :Artist %>
<%= f.text_field :name %>
<%= fields_for :paintings, #artist.paintings do |artist_paintings| %>
<%= artist_paintings.label :title %>
<%= artist_paintings.text_field :title %>
<%= artist_paintings.label :image %>
<%= artsist_paintings.text_field :image %>
</fieldset>
<%= f.submit %>
<% end %>
Note:
You should be having your Artist Controller with at least new,create,edit and update methods defined in it to achieve this.
Edit
Try the reverse
Artist
has_many :paintings, :dependent => :destroy
Painting
belongs_to :artist
accepts_nested_attributes_for :paintings
form_for(#painting) do |f| %>
<fieldset>
<legend>Painting</legend>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :image %>
<%= f.text_field :image %>
<%= fields_for :artists, #painting.artists do |ff| %>
<%= ff.label :Artist %>
<%= ff.text_field :name %>
</fieldset>
<%= f.submit %>
<% end %>
Put this form in paintings views.

Can't mass-assign protected attributes: quantities

I'm getting this error:
Can't mass-assign protected attributes: quantities
I looked up all the threads concerning this issue in the site, but couldn't find something to answer my problem. Here are the code snippets:
product.rb
class Product < ActiveRecord::Base
attr_accessible :name, :quantities_attributes
has_many :quantities
accepts_nested_attributes_for :quantities, :allow_destroy => :true,
:reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
new.html.erb
<% if #was_submitted %>
<%= form_for(:new_product_array, :url => products_path) do |f| %>
<% prefix ||= 0 %>
<% #new_product_array.each do |n| %>
<% n.quantities.build %>
<% prefix += 1 %>
<%= f.fields_for(prefix.to_s ) do |child| %>
<div class="field">
<%= child.label :name %><br />
<%= child.text_field :name%>
</div>
<%= render :partial => 'quantities/form',
:locals => {:form => child} %>
<% end %>
<% end %>
<div class="actions">
<%= submit_tag :submit %>
</div>
<% end %>
<% else %>
<%= form_tag new_product_path, :method => 'get' do %>
<p align=center>
How many Items are you Adding? (1-100)
<%= number_field_tag 'amount', 1, :in => 1...100 %>
</br>
To which storage?
<%= number_field_tag 'storage', 1, :in => 1...100 %>
<%= submit_tag "Next", :name => 'submitted' %>
</p>
<% end %>
<% end %>
<%= link_to 'Back', products_path %>
product_controller.rb
def new
#product = Product.new
if params['submitted']
#was_submitted = true
#amount_form = params['amount']
#new_product_array = []
(1..#amount_form.to_i).each do
#new_product_array << Product.new
end
#storage_form = params['storage']
else
#was_submitted = false
end
respond_to do |format|
format.html # new.html.erb
format.json { render :json => #product }
end
end
def create
i=0
logger.info params[:new_product_array].inspect
params[:new_product_array].each do |new_product|
if new_product.last[:name] != nil
#new_product_array[i] = Product.new(new_product.last)
#new_product_array[i].save
i+=1
end
end
redirect_to(products_path)
end
quantity.rb
class Quantity < ActiveRecord::Base
belongs_to :product
attr_accessible :amount, :storage
end
quantity/_form.html.erb
<%= form.fields_for :quantities do |quant| %>
<div class="field">
<%= quant.label :storage %><br />
<%= quant.number_field :storage %>
</div>
<div class="field">
<%= quant.label :amount %><br />
<%= quant.number_field :amount %>
</div>
<% unless quant.object.nil? || quant.object.new_record? %>
<div class="field">
<%= quant.label :_destroy, 'Remove:' %>
<%= quant.check_box :_destroy %>
</div>
<% end %>
<% end %>
Overall what Im trying to do, is ask the user how much products to add, then make a form with the number of fields the user specifies and with one submit button add all of the products, whereas when you add a product you also add a quantity record which holds more information on the product.
You need a line like this:
attr_accessible :name, :quantities_attributes, :quantities
You have very bad code, it can be much simplier 100%.
Your problem is that form dont' know nothing about your resource (product), so it can't render 'smartly' fields "quantities_attributes", it renders "quantities" instead.

partial form for nested models in rails 3

I have two models pages and author, here is the code of model pages:
edit -1
now my models are as follows:
class Page < ActiveRecord::Base
validates :title, :presence => true
belongs_to :author
end
author model:
class Author < ActiveRecord::Base
has_many :pages
end
and my form is as follows:
<%= form_for(#page) do |f| %>
<% if #page.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#page.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% #page.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>
<p>
<%= f.label :body %><br />
<%= f.text_area :body %>
</p>
<p>
<%= f.fields_for :author do |fields| %>
<%= f.label :author %><br />
<%= fields.text_field :author %>
<% end %>
</p>
<p>
<%= f.label :email %><br />
<%= f.text_field :email %>
</p>
<p>
<%= f.label :reference %><br />
<%= f.select(:reference,[['google',1],['yahoo',2],['MSN',3],['Ask',4]]) %>
</p>
<%= f.submit "Submit" %>
<% end %>
and controller :
class PagesController < ApplicationController
def index
#total = Page.count
#pages = Page.find(:all)
end
def show
#page = Page.find(params[:id])
end
def new
#page = Page.new
end
def create
#page = Page.new(params[:page])
if #page.save
redirect_to pages_path, :notice => "The data has been saved!"
else
render "new"
end
end
def edit
#page = Page.find(params[:id])
end
def update
#page = Page.find(params[:id])
if #page.update_attributes(params[:page])
redirect_to pages_path, :notice => "Your post has been updated!"
else
render "edit"
end
end
def destroy
#page = Page.find(params[:id])
#page.destroy
redirect_to pages_path, :notice => "Your page has been deleted!"
end
end
Now when i am submitting the form it is giving me this error:
ActiveRecord::AssociationTypeMismatch in PagesController#create
Author(#40328004) expected, got ActiveSupport::HashWithIndifferentAccess(#32291496)
Rails.root: C:/rorapp
Application Trace | Framework Trace | Full Trace
app/controllers/pages_controller.rb:19:in `new'
app/controllers/pages_controller.rb:19:in `create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"hzBlXsdUjEDDCLp036R8bJBwep6BdATSvJPNwt0M8Dg=",
"page"=>{"title"=>"",
"body"=>"",
"author"=>{"author"=>""},
"email"=>"",
"reference"=>"1"},
"commit"=>"Submit"}
Show session dump
Show env dump
Response
Headers:
None
and one more problem if I add accepts_nested_attributes_for :author to my Page model, then the field is not displayed at all. I really want to accomplish this.. any help?
Apparently this doesn't work in reverse with the association. I'm sorry for the mistake. You could do the following, but then your pages controller is acting on the author, which isn't really appropriate. You could make an author controller though, and include fields_for :pages, to have the author and the first page created at the same time.
class PagesController < ApplicationController
def new
#author = Author.new
#page = #author.pages.new
end
def create
#author = Author.create(params[:author])
end
end
class Author < ActiveRecord::Base
has_many :pages
accepts_nested_attributes_for :pages
end
class Page < ActiveRecord::Base
belongs_to :author
end
<%= form_for(#author, :url => pages_url) do |f| %>
<%= f.text_field :author %>
<%= f.fields_for :pages do |fields| %>
<%= fields.text_area :body %>
<% end %>
<% end %>

Resources