rails g model reward campaign:references title amount:integer
rake db:migrate
in campaign model, set up the association and nested attributes
the reject_if: :all_blank will ignore this input(not throwing any validation error) if user didn't put in any value for this reward object
has_many :rewards , dependent : :destroy
# this enables us to create associated rewards models at the same time we're
# creating the campaign model.
# reject_if: :all_blank means that if the user leaves all the fields for the
# reward empty, it will be ignored and not passed to the
# validation
# allow_destroy: true means that if you pass in a special attributes _destroy
# with value `true` as part of the `reward` params it
# will delete the reward record all together.
accepts_nested_attributes_for :rewards , reject_if : :all_blank , allow_destroy : true
edit the simple form for campaign
<%= simple_form_for @campaign do |f| %>
<%= f.input :name, label: "Campaign Name", input_html: {class: "small-field"} %>
<%= f.input :description %>
<%= f.input :goal, as: :string %>
<%= f.file_field :image %>
<%= f.input :end_date %>
<%= f.simple_fields_for :rewards do |r| %>
<%= r.input :title %>
<%= r.input :amount, as: :string %>
<% end %>
<%= f.submit class: "btn btn-primary" %>
<% end %>
use the build method to create empty rewards input fields in the campaign form
2.2.3 :006 > 3.times {c.rewards.build}
3
2.2.3 :007 > c.rewards
Reward Load (0.3ms) SELECT " rewards" .* FROM " rewards" WHERE " rewards" ." campaign_id" = $1 [[" campaign_id" , 5]]
+----+-------------+-------+--------+------------+------------+
| id | campaign_id | title | amount | created_at | updated_at |
+----+-------------+-------+--------+------------+------------+
| | 5 | | | | |
| | 5 | | | | |
| | 5 | | | | |
| | 5 | | | | |
+----+-------------+-------+--------+------------+------------+
4 rows in set
2.2.3 :008 >
in the campaigns controller, add the following to new action
3 . times { @campaign . rewards . build }
and permit the campaign attributes/rewards_attributes
campaign_params = params . require ( :campaign ) . permit ( [ :name , :goal , :image , :end_date , :description ,
{ rewards_attributes : [ :amount , :title ] } ] )
add the following in campaign show
< h2 > Rewards</ h2 >
<% @campaign . rewards . each do |r | %>
< div class ="well col-md-3 ">
< h3 > <%= r . title %> </ h3 >
< p > <%= number_to_currency ( r . amount ) %> </ p >
</ div >
<% end %>
in order to prevent adding empty rewards in the database we can add in the following validations:
validates :title , presence :true
validates :amount , presence :true , numericality : { greater_than : 0 }
further polish: keep the input for 2nd or 3rd reward if user make mistake in the 1st rewards field
REWARD_COUNT = 3
def new
@campaign = Campaign . new
REWARD_COUNT . times { @campaign . rewards . build }
end
def create
@campaign = Campaign . new ( campaign_params )
...
if @campaign . save
...
else
number_to_build = REWARD_COUNT - @campaign . rewards . size
number_to_build . times { @campaign . rewards . build }
...
render :new
end
end
when editing campaign, reward attributes also come with reward id. therefore we must permit it in the rewards param
campaign_params = params . require ( :campaign ) . permit ( [ :name , :goal , :image , :end_date , :description ,
{ rewards_attributes : [ :id , :amount , :title ] } ] )
add a input field to the end of the form
<%= r.input :_destroy, as: :boolean %>
campaign_params = params . require ( :campaign ) . permit ( [ :name , :goal , :image , :end_date , :description ,
{ rewards_attributes : [ :id , :amount , :title , :_destroy ] } ] )
refactor build reward input fields
REWARD_COUNT = 3
def build_associated_rewards
number_to_build = REWARD_COUNT - @campaign . rewards . size
number_to_build . times { @campaign . rewards . build }
end
...
def new
@campaign = Campaign . new
build_associated_rewards
end
...
def edit
build_associated_rewards
end
create it in the rails console
c = Campaign.new(name:" hello" , description:" test" , goal:50, rewards_attributes: {" 0" => {" title" => " book1" , " amount" => " 30" , " _destroy" => " 0" , " id" => " 1" }, " 1" => {" title" => " tshirt" , " amount" => " 40" , " _destroy" => " 0" , " id" => " 2" }, " 2" => {" title" => " earphone haha" , " amount" => " 50" , " _destroy" => " 1" , " id" => " 3" }}}
Dynamically add associated attributes with form
add to application.js after jquery
create a partial in view/campaigns name it _rewards_field.html.erb
< div class ="nested-fields ">
<%= f . input :title %>
<%= f . input :amount , as : :string %>
<%= link_to_remove_association "remove reward" , f %>
< hr >
</ div >
<%= simple_form_for @campaign do |f| %>
<%= f.input :name, label: "Campaign Name", input_html: {class: "small-field"} %>
<%= f.input :description %>
<%= f.input :goal, as: :string %>
<%= f.file_field :image %>
<%= f.input :end_date %>
<div id ="rewards ">
<%= f . simple_fields_for :rewards do |r | %>
<%= render "reward_fields" , f : r %>
<% end %>
< div class ="links ">
<%= link_to_add_association "Add Reward" , f , :rewards %>
</ div >
</ div >
<%= f . submit class : "btn btn-primary" %>
<% end %>