Single form for parent and all children in relationship? - ruby-on-rails

In my RoR I have a table games, stats, and players. Each game has many players, each player has many 'stats,' and each games has many stats through players. What i want to be able to do is in on my edit games form, i want there to be a a row of fields to add a new stat row, one per each players the game has. Ive been reading a lot about nested_attributes, but found new good resources how to fully do this.

UPDATE: Here's an updated set of classes based on the new associations you've stated in your comment
# models/game.rb
class Game < ActiveRecord::Base
has_many :teams
accepts_nested_attributes_for :teams
attr_accessible :name, :teams_attributes
end
# models/team.rb
class Team < ActiveRecord::Base
belongs_to :game
has_many :players
accepts_nested_attributes_for :players
attr_accessible :name, :players_attributes
end
# models/player.rb
class Player < ActiveRecord::Base
belongs_to :team
has_many :stats
accepts_nested_attributes_for :stats, reject_if: proc { |attributes| attributes['name'].blank? }
attr_accessible :name, :stats_attributes
end
# models/stat.rb
class Stat < ActiveRecord::Base
belongs_to :player
attr_accessible :name
end
# controllers/games_controller.rb
class GamesController < ApplicationController
def edit
#game = Game.find(params[:id])
#game.teams.each do |team|
team.players.each do |player|
player.stats.build
end
end
end
def update
#game = Game.find(params[:id])
if #game.update_attributes(params[:game])
render "show"
else
render text: "epic fail"
end
end
end
# games/edit.html.erb
<%= form_for #game do |f| %>
<%= f.fields_for :teams do |tf| %>
<p>Team: <%= tf.object.name %></p>
<%= tf.fields_for :players do |pf| %>
<p>Player: <%= pf.object.name %></p>
<%= pf.fields_for :stats do |sf| %>
<%= sf.text_field :name %>
<% end %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
Note this doesn't do any sort of ajax "add another stat" or anything fancy. It just sticks one extra blank field at the end for each player. If you needed more, you could build more blank stat objects in the GamesController#edit action or implement some fancy pants javascript. Hopefully that will get you close enough to be able to get your real data working.

http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
without seeing more of your own code, I can't be specific, but basically, you loop over all the players and "build" the stats... game.players.each {|p| p.build_stat} and then in the form, loop over all the players again, and display the stats (maybe limiting to the new_record? ones?) Or perhaps do the build right in the form so it shows a blank entry.
I think I see a potential problem though, with your models... If the stats are a specific representation of a specific game, then your model as you describe it doen't link them - you'd need a game_id and player_id in each stat record. If that were the case, you would build all the stats in the controller method, and loop over them in the view.

Related

Rails nested form for has_many :through with an additional field on the join model using Simple Form

I'm trying to do a nested form for a has_many :through association using Simple Form, and I can't figure out how to get around this error: ArgumentError in Variants#edit -- Association cannot be used in forms not associated with an object.
Here's what I'm trying to accomplish. I have a "Product Variant" model (called Variant). Each variant can have many parts (Part model) through a "Parts List Item" (PartsListItem) join model. Each variant should be able to have parts assigned to it in different quantities.
For instance, a guitar strap might have a part called "Backing Fabric" that has a quantity of 1. Meaning that the Guitar Strap variant needs 1 of the "Backing Fabric" part to be assembled. But the same variant might also have another part such as "Rivet" that has a quantity of 4. (As in 4 rivets are required to make this product variant.) After using the Variant form to add all the parts in various quantities to the variant, I'd like to show all of the parts with quantities on the variants#show page.
Here is the relevant code from my models:
class Variant < ApplicationRecord
has_many :parts_list_items, dependent: :destroy
has_many :parts, through: :parts_list_items, dependent: :nullify
accepts_nested_attributes_for :parts
end
class PartsListItem < ApplicationRecord
belongs_to :variant
belongs_to :part
end
class Part < ApplicationRecord
has_many :parts_list_items, dependent: :destroy
has_many :variants, through: :parts_list_items, dependent: :nullify
end
And my VariantsController:
class VariantsController < ApplicationController
def update
respond_to do |format|
if #variant.update(variant_params)
format.html { redirect_to #variant, notice: 'Variant was successfully updated.' }
else
format.html { render :edit }
end
end
end
private
def variant_params
params.require(:variant).permit(:account_id, :product_id, :sku,
:choice_ids => [], :part_ids => [])
end
end
And my form (views/variants/_edit_form.html.erb):
<%= simple_form_for #variant do |f| %>
<%= f.simple_fields_for :parts_list_items do |item| %>
<%= item.input_field :quantity %>
<%= item.association :parts %>
<% end %>
<% end %>
Note that this works just fine:
<%= simple_form_for #variant do |f| %>
<%= f.association :parts, as: :check_boxes %>
<% end %>
So, it works to associate parts directly to the variant through the PartsListItem join model. The trouble begins when I start trying to add the quantity for each associated part.
What am I doing wrong with this nested form? Is there a problem with my controllers or associations?
Do I need to create an additional model called PartsList that has_many :parts_list_items with additional associations? That seems like an extra step and that there should be a way to put the :quantity on the PartsListItem model.
I think you need to change parts to part
<%= simple_form_for #variant do |f| %>
<%= f.simple_fields_for :parts_list_items do |item| %>
<%= item.input_field :quantity %>
<%= item.association :parts %> <!-- HERE -->
<% end %>
<% end %>

Rate a post only once per user in rails

I have trouble in allowing users to rate a post. My task is to enable the user to rate a post only once. On the show page, the post I have includes radio buttons for rating. If the user tries to rate for the second time it needs to update the previous rating done by the user for the same post. The issue I am facing is that user is able to rate a post multiple times. How to resolve this?
User model:
class User < ApplicationRecord
has_many :posts
has_many :ratings
end
Post model:
class Post < ApplicationRecord
has_many :ratings
belongs_to :user
end
Ratings model
class Rating < ApplicationRecord
belongs_to :post
belongs_to :user
end
In the post controllers i have used nested attributues for ratings.
def show
#post = #topic.posts.find(params[:id])
#rate = #post.ratings.all
#rate = Rating.where(post_id: #post.id).group("rate").count
end
private def post_params
params.require(:post).permit(:title, :body, ratings_attributes: [:rate])
end
The show page of post include the creating of rating using <fieldset>:
<%= form_for [#topic, #post] do |f| %>
<%= f.fields_for :ratings, #post.ratings.build do |builder| %>
<fieldset>
<% for i in 1..5 %>
<%= builder.radio_button :rate, i %><%= i %>
<% end %>
</fieldset>
<% end %>
<%=f.submit "Rate" %>
<% end %>
First, add validation to the Rating to enforce uniqueness on the combination of user and post. This will stop a duplicate rating ever being created.
validates_uniqueness_of :post_id, scope: :user_id
Then, in the action that saves the rating, first check if there is a record that can be updated, else create a new one.
#rating = Rating.find_or_initialize_by(user: #user, post: #post)
#rating.rate = params[:rate]
#rating.save
This might not be perfect syntax, but you should get the idea about what you are trying to do and can adjust to match your code.
You can use first or initialize like this
#rating=Rating.where(post_id: #post.id,user_id: current_user).first_or_initialize

Rails nested models and child validation

I have two models.
Exemplary:
class Book < ActiveRecord::Base
has_many :pages, dependent: :destroy
accepts_nested_attributes_for :pages, allow_destroy: true
end
class Page < ActiveRecord::Base
belongs_to :book
validate_on_create :count_within_bounds
LIMIT = 200
private
def count_within_bounds
if self.book.pages.count >= LIMIT
errors.add_to_base("Number of pages cannot be greater than #{LIMIT}")
end
end
end
Now when update the book through a nested form everything is working just fine. I can edit let's say the title and add new pages. But if the page validation fails the other changes made to the book model are not getting saved either.
I understand that it's all being saved in one transaction but is there a way to persist the parent regardless without having to do it manually in two steps, i.e. saving the parent first without pages_attributes?
You can take away the validation and do something like:
<%= form_for(#book) do |f| %>
# book attribute stuff....
<% if #book.pages.count < 200 %>
<%= f.fields_for :pages, #book.pages.create do |ff| %>
<%= ff.text_field :attribute %><br>
<% end %>
<% end %>
<% end %>
Now only books with less than 200 pages will get a form that includes fields to add pages.
If you want to save the parent only and skip the validations for your Page subclass you can use the validate option in your Book model.
class Book < ActiveRecord::Base
has_many :pages, validate: false, dependent: :destroy
end

has_many through form new method, no id, no elements

I've been hacking around with Rails 3.2.11 for a while, and am trying to do this the 'right' way.
I have three models (Reflection, Skill, Utilization) that relate to each other through has_many: through:
Utilization.rb
class Utilization < ActiveRecord::Base
attr_accessible :reflection, :skill, :used_skill #used_skill is a boolean
belongs_to :reflection
belongs_to :skill
end
Reflection.rb
class Reflection < ActiveRecord::Base
## attributes here ##
has_many :utilizations
has_many :skills, through: :utilizations
accepts_nested_attributes_for :utilizations
accepts_nested_attributes_for :skills
end
Skill.rb
class Skill < ActiveRecord::Base
## attributes here ##
has_many :utilizations
has_many :reflections, through: :utilizations
end
Within the app, skills are already defined. The user action I am trying to support is:
User gets form for new Reflection.
User sees a list of Skills and checks off which ones they have used (Utilization).
User posts to create new Reflection and create the associated Utilization objects.
Here is the new method reflection_controller.rb:
class ReflectionsController < ApplicationController
def new
#reflection = Reflection.new
Skill.all.each do |skill|
#reflection.utilizations.build(skill_id: skill.id, used_skill: false)
end
end
end
And an abbreviated _form.html.erb for Reflections
<%= form_for(#reflection) do |f| %>
<% f.fields_for :utilizations do |builder| %>
<%= builder.label :used_skill %>
<%= builder.check_box :used_skill %>
<%= builder.fields_for :skill do |skill| %>
<%= skill.label :description %>
<%= skill.text_field :description %>
<% end %>
<% end %>
<% end %>
So the problem is that even though there are multiple Skills and I .new the Utilization objects and associate them with the #reflection, they don't show up in the form. I've played with the data structures a little bit, and I can reach the point where in ReflectionController.new #reflection.utilizations contains Utilization objects, it still won't work; when I run #reflection.utilizations.count it returns 0. It looks like the problem is that since none of the objects have an id at that time, it simply will not render out in the form. But my understanding is that one should not create objects during the new method…
Is there something obvious I'm missing? Is there a better way to do this? I've seen examples, include Ryan Bates' Railscast where people just use code like:
def new
#survey = Survey.new
3.times do
question = #survey.questions.build
4.times { question.answers.build }
end
end
and supposedly this works fine.
I really appreciate the help. Trying to figure this out has been driving me crazy. This is my first question on SO, and I'm happy to add any clarifying data or additional code if you think it would help.
You forgot to use =:
<%#### Here ####%>
<%= f.fields_for :utilizations do |builder| %>
<%= builder.label :used_skill %>
<%= builder.check_box :used_skill %>
<%#### and here ####%>
<%= builder.fields_for :skill do |skill| %>

fields_for and has_many_through

I have these models:
class Order < ActiveRecord::Base
has_many :order_lines
has_many :prizes, :through => :order_lines
accepts_nested_attributes_for :order_lines
end
class Prize < ActiveRecord::Base
has_many :order_lines
end
class OrderLine < ActiveRecord::Base
belongs_to :order
belongs_to :prize
end
I would like a nested form in the order form that displays every prize with a text box next to it where the user can enter pieces (eg. the amount to order). When the form is submitted the create action should create the order_lines accordingly. I can't find a solution anywhere.
First of all in the Order model use accepts_nested_attributes_for :prizes, instead of what you have.
After that it's easy, just add a form in the view (there's no additional step controller)
<%= form_for #order do |order_form| %>
..
<%= order_form.fields_for :prizes do |prizes_form| %>
<%= prizes_form.text_field :piece %>
..
<% end %>
..
<% end %>
This is straight from the documentation... You should definitely check that first.

Resources