Skip to content

Instantly share code, notes, and snippets.

@jennli
Last active March 15, 2016 21:15
Show Gist options
  • Save jennli/1f68f14644b930ea9918 to your computer and use it in GitHub Desktop.
Save jennli/1f68f14644b930ea9918 to your computer and use it in GitHub Desktop.
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 >

new

  • in the campaigns controller, add the following to new action
3.times {@campaign.rewards.build}

create

  • and permit the campaign attributes/rewards_attributes
campaign_params = params.require(:campaign).permit([:name, :goal, :image, :end_date, :description, 
{rewards_attributes: [:amount, :title]}])

show

  • 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

edit/Update

  • 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]}])

destroy

  • add a input field to the end of the form
 <%= r.input :_destroy, as: :boolean %>
  • permit it in params
 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

  • gem cocoon
gem "cocoon"
  • add to application.js after jquery
//= require cocoon
  • 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>
  • modify the campaign form
<%= 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 %>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment