Rails nested models and child validation - ruby-on-rails

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

Related

How to setup a deeply nested form in Rails 3.2

I'm trying to build a rather complex nested form in rails and am stuck.
Basically, I have three models - Applicant, DataPoint, ApplicantDataPointValue .
The user can create a new DataPoint, give it a name ("gender" etc.) select it's type ("string","integer" etc.). The type determines what column the data will eventually be saved in in the ApplicantDataPointValue table.
I then want the user, when they're creating a new Applicant, to be able to add a value for each DataPoint into the ApplicantDataPointValue table
My models look like the following:
Applicant:
class Applicant < ActiveRecord::Base
has_many :applicant_data_point_values, dependent: :destroy
has_many :data_points, :through => :applicant_data_point_values
accepts_nested_attributes_for :data_points
accepts_nested_attributes_for :applicant_data_point_values
attr_accessible :data_points_attributes, :applicant_data_point_values_attributes
end
DataPoint:
class DataPoint < ActiveRecord::Base
has_many :applicant_data_point_values
has_many :applicants, :through => :applicant_data_point_values
accepts_nested_attributes_for :applicant_data_point_values
end
ApplicantDataPointValue:
class ApplicantDataPointValue < ActiveRecord::Base
belongs_to :data_point
belongs_to :applicant
end
But I'm at a loss to what to do in the 'new' and 'create' sections of my controller or how to construct the form.
Any insight would be greatly appreciated.
From what I understand, the form for the User will also have multiple ApplicantDataPointValue fields. (but that form won't allow creating of new DataPoint fields, right?)
In the controller new action, you'll want to set up your model with associated data point values:
def new
#user = User.new
DataPoint.all.each do |data_point|
applicant_data_point_value = #user.applicant_data_point_values.build
applicant_data_point_value.data_point = data_point
end
end
And then, display a text box for each data point value.
<%= form_for #user do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<% #user.applicant_data_point_values.each do |data_point_value| %>
<%= f.fields_for :applicant_data_point_values, data_point_value do |fields| %>
<%= fields.label :value, data_point_value.data_point.type %>
<%= fields.text_field :value %>
<% end %>
<% end %>
Reference: http://railscasts.com/episodes/196-nested-model-form-part-1

How do I save many-to-many relationships in a model

I have the following setup in my rails app:
A user registers and he chooses from a set of check boxes for Music Styles.
The Music Styles are only 4 right now but should be extendable. I'd like to have a list of MusicStyles that I can extend and change easily.
My approach would be to create a model 'MusicStyles' and a model 'UserMusicStyles' and then use a has_many_through association similar to:
class User < ActiveRecord::Base
has_many :user_music_styles
has_many :music_styles, :through => :user_music_styles
end
class UserMusicStyle < ActiveRecord::Base
belongs_to :user
belongs_to :music_style
end
class MusicStyle < ActiveRecord::Base
has_many :music_styles
has_many :users, :through => :user_music_styles
end
Now, during registration I would do something like MusicStyle.all.each do |m| ... to display the checkboxes but how do I save it to the database correctly in the user controller?
Any help much appreciated!
You can do it like this:
<%= form_for #user do |f| %>
<!-- User stuff -->
...
<% MusicStyle.all.each do |m| %>
<%= check_box_tag('user[music_style_ids][]', m.id, #user.music_styles.include?(m)) %>
<% end %>
<%= f.submit 'Save' %>
<% end %>

Single form for parent and all children in relationship?

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.

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.

nested forms and one to one relationship

I have a one-to-one relationship between user and goal. I want to build a form that shows the goal of a user. The problem is that my code only works when the user already has a goal defined. The text field is not rendered when no goal is present.
<%= user_builder.fields_for :goal do |goal_builder| %>
<%= goal_builder.text_field :goal %>
<% end %>
Does Rails provide an easy way to do this?
This is how I would do it :
class User < ActiveRecord::Base
has_one :goal
accepts_nested_attributes_for :goal
after_initialize do
self.goal ||= self.build_goal()
end
end
You can do this very easily with accepts_nested_attributes_for.
In the view, as you had:
<%= user_builder.fields_for :goal do |goal_builder| %>
<%= goal_builder.text_field :goal %>
<% end %>
In the User model:
class User < ActiveRecord::Base
has_one :goal # or belongs_to, depending on how you set up your tables
accepts_nested_attributes_for :goal
end
See the documentation on nested attributes, and the form_for method for more information.

Resources