GitHub repo: https://github.com/Yorkshireman/mywordlist
I've googled the hell out of this one. I'm sure there's a way, possibly requiring some code inside a html options hash, but I can't work it out. Any ideas?
When visiting the _edit_word_form.html.erb partial for a Word that DOES have one or more categories, the Categories checkboxes are all unchecked, requiring the user to select them again, even if they don't want to change the Categories.
The text fields for the :title and :description ARE pre-populated (thankfully).
_edit_word_form.html.erb:
<%= form_for(#word) do %>
<%= fields_for :word, #word do |word_form| %>
<div class="field form-group">
<%= word_form.label(:title, "Word:") %><br>
<%= word_form.text_field(:title, id: "new_word", required: true, autofocus: true, class: "form-control") %>
</div>
<div class="field form-group">
<%= word_form.label(:description, "Definition:") %><br>
<%= word_form.text_area(:description, class: "form-control") %>
</div>
<% end %>
<%= fields_for :category, #category do |category_form| %>
<% if current_user.word_list.categories.count > 0 %>
<div class="field form-group">
<%= category_form.label(:title, "Choose from existing Categories:") %><br>
<%= category_form.collection_check_boxes(:category_ids, current_user.word_list.categories.all, :id, :title) do |b| %>
<%= b.label(class: "checkbox-inline") { b.check_box + b.text } %>
<% end %>
</div>
<% end %>
<h4>AND/OR...</h4>
<div class="field form-group">
<%= category_form.label(:title, "Create and Use a New Category:") %><br>
<%= category_form.text_field(:title, class: "form-control") %>
</div>
<% end %>
<div class="actions">
<%= submit_tag("Update!", class: "btn btn-block btn-primary btn-lg") %>
</div>
<% end %>
Relevant part of the words/index.html.erb:
<% current_user.word_list.words.alphabetical_order_asc.each do |word| %>
<tr>
<td>
<%= link_to edit_word_path(word) do %>
<%= word.title %>
<span class="glyphicon glyphicon-pencil"></span>
<% end %>
</td>
<td><%= word.description %></td>
<td>
<% word.categories.alphabetical_order_asc.each do |category| %>
<a class="btn btn-info btn-sm", role="button">
<%= category.title %>
</a>
<% end %>
</td>
<td>
<%= link_to word, method: :delete, data: { confirm: 'Are you sure?' } do %>
<span class="glyphicon glyphicon-remove"></span>
<% end %>
</td>
</tr>
<% end %>
words_controller.rb:
class WordsController < ApplicationController
before_action :set_word, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
# GET /words
# GET /words.json
def index
#words = Word.all
#quotes = Quote.all
end
# GET /words/1
# GET /words/1.json
def show
end
# GET /words/new
def new
#word = current_user.word_list.words.build
end
# GET /words/1/edit
def edit
end
# POST /words
# POST /words.json
def create
#word = Word.new(word_params)
respond_to do |format|
if #word.save
format.html { redirect_to #word, notice: 'Word was successfully created.' }
format.json { render :show, status: :created, location: #word }
else
format.html { render :new }
format.json { render json: #word.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /words/1
# PATCH/PUT /words/1.json
def update
#need to first remove categories from the word
#word.categories.each do |category|
#word.categories.delete category
end
#then push categories in from the category_params
if params["category"].include?(:category_ids)
(params["category"])["category_ids"].each do |i|
next if i.to_i == 0
#word.categories << Category.find(i.to_i) unless #word.categories.include?(Category.find(i.to_i))
end
end
if category_params.include?(:title) && ((params["category"])["title"]) != ""
#word.categories << current_user.word_list.categories.build(title: (params["category"])["title"])
end
respond_to do |format|
if #word.update(word_params)
format.html { redirect_to words_path, notice: 'Word was successfully updated.' }
format.json { render :show, status: :ok, location: #word }
else
format.html { render :edit }
format.json { render json: #word.errors, status: :unprocessable_entity }
end
end
end
# DELETE /words/1
# DELETE /words/1.json
def destroy
#word.destroy
respond_to do |format|
format.html { redirect_to words_url, notice: 'Word was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_word
#word = Word.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def word_params
params.require(:word).permit(:title, :description, :category_ids)
end
def category_params
params.require(:category).permit(:title, :category_ids, :category_id)
end
end
categories_controller.rb:
class CategoriesController < ApplicationController
before_action :set_category, only: [:show, :edit, :update, :destroy]
# GET /categories
# GET /categories.json
def index
#categories = Category.all
end
# GET /categories/1
# GET /categories/1.json
def show
end
# GET /categories/new
def new
#category = current_user.word_list.categories.build
end
# GET /categories/1/edit
def edit
end
# POST /categories
# POST /categories.json
def create
#category = Category.new(category_params)
respond_to do |format|
if #category.save
format.html { redirect_to #category, notice: 'Category was successfully created.' }
format.json { render :show, status: :created, location: #category }
else
format.html { render :new }
format.json { render json: #category.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /categories/1
# PATCH/PUT /categories/1.json
def update
respond_to do |format|
if #category.update(category_params)
format.html { redirect_to #category, notice: 'Category was successfully updated.' }
format.json { render :show, status: :ok, location: #category }
else
format.html { render :edit }
format.json { render json: #category.errors, status: :unprocessable_entity }
end
end
end
# DELETE /categories/1
# DELETE /categories/1.json
def destroy
#category.destroy
respond_to do |format|
format.html { redirect_to categories_url, notice: 'Category was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_category
#category = Category.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def category_params
params.require(:category).permit(:title, :word_list_id, :category_ids)
end
end
word_lists_controller.rb:
class WordListsController < ApplicationController
before_action :set_word_list, only: [:show, :edit, :update, :destroy]
def from_category
#selected = current_user.word_list.words.joins(:categories).where( categories: {id: (params[:category_id])} )
respond_to do |format|
format.js
end
end
def all_words
respond_to do |format|
format.js
end
end
# GET /word_lists
# GET /word_lists.json
def index
#word_lists = WordList.all
end
# GET /word_lists/1
# GET /word_lists/1.json
def show
#words = Word.all
#word_list = WordList.find(params[:id])
end
# GET /word_lists/new
def new
#word_list = WordList.new
end
# GET /word_lists/1/edit
def edit
end
# POST /word_lists
# POST /word_lists.json
def create
#word_list = WordList.new(word_list_params)
respond_to do |format|
if #word_list.save
format.html { redirect_to #word_list, notice: 'Word list was successfully created.' }
format.json { render :show, status: :created, location: #word_list }
else
format.html { render :new }
format.json { render json: #word_list.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /word_lists/1
# PATCH/PUT /word_lists/1.json
def update
respond_to do |format|
if #word_list.update(word_list_params)
format.html { redirect_to #word_list, notice: 'Word list was successfully updated.' }
format.json { render :show, status: :ok, location: #word_list }
else
format.html { render :edit }
format.json { render json: #word_list.errors, status: :unprocessable_entity }
end
end
end
# DELETE /word_lists/1
# DELETE /word_lists/1.json
def destroy
#word_list.destroy
respond_to do |format|
format.html { redirect_to word_lists_url, notice: 'Word list was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_word_list
#word_list = WordList.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def word_list_params
params[:word_list]
end
end
word_list.rb:
class WordList < ActiveRecord::Base
belongs_to :user
has_many :words
has_many :categories
end
word.rb:
class Word < ActiveRecord::Base
belongs_to :word_list
has_and_belongs_to_many :categories
validates :title, presence: true
scope :alphabetical_order_asc, -> { order("title ASC") }
end
category.rb:
class Category < ActiveRecord::Base
has_and_belongs_to_many :words
belongs_to :word_list
validates :title, presence: true
scope :alphabetical_order_asc, -> { order("title ASC") }
end
schema.rb:
ActiveRecord::Schema.define(version: 20150609234013) do
create_table "categories", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "word_list_id"
end
add_index "categories", ["word_list_id"], name: "index_categories_on_word_list_id"
create_table "categories_words", id: false, force: :cascade do |t|
t.integer "category_id"
t.integer "word_id"
end
add_index "categories_words", ["category_id"], name: "index_categories_words_on_category_id"
add_index "categories_words", ["word_id"], name: "index_categories_words_on_word_id"
create_table "quotes", force: :cascade do |t|
t.text "content"
t.string "author"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
create_table "word_lists", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
create_table "words", force: :cascade do |t|
t.string "title"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "word_list_id"
end
add_index "words", ["word_list_id"], name: "index_words_on_word_list_id"
end
routes.rb:
Rails.application.routes.draw do
resources :quotes
resources :categories
resources :words
devise_for :users, controllers: { registrations: "users/registrations" }
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
# You can have the root of your site routed with "root"
# root 'welcome#index'
root 'pages#home'
post 'create_word_and_category' => 'new_word#create_word_and_category'
end
The discussion might not be active anymore but I'll share my answer for the future visitors.
Adding "{ checked: #array.map(&:to_param) }" option as the last argument of collection_check_boxes may resolve your problem. Refer this link.
Example:
Suppose you have Software model and Platform(OS) model and want to choose one or more OS that support your software.
#views/softwares/edit.html.erb
<%= form_for #software do |f| %>
...
<%= f.label :supported_platform %>
<%= f.collection_check_boxes(:platform_ids, #platforms, :id, :platform_name, { checked: #software.platform_ids.map(&:to_param) }) %>
...
<%= f.submit "Save", class: 'btn btn-success' %>
<% end %>
note:
#software.platform_ids should be an array. If you are using SQLite, you have to convert string into array when you pull the data out. I tested it with SQLite and confirmed working as well. See my post for more detail.
This should be closer to what you were looking for:
<%= b.label(class: "checkbox-inline", :"data-value" => b.value) { b.check_box + b.text } %>
Try changing your _edit_word_form.html.erb like this
<%= form_for(#word) do |f| %>
<div class="field form-group">
<%= f.label(:title, "Word:") %><br>
<%= f.text_field(:title, id: "new_word", required: true, autofocus: true, class: "form-control") %>
</div>
<div class="field form-group">
<%= f.label(:description, "Definition:") %><br>
<%= f.text_area(:description, class: "form-control") %>
</div>
<%= f.fields_for :category do |category_form| %>
<% if current_user.word_list.categories.count > 0 %>
<div class="field form-group">
<%= category_form.label(:title, "Choose from existing Categories:") %><br>
<%= category_form.collection_check_boxes(:category_ids, current_user.word_list.categories.all, :id, :title) do |b| %>
<%= b.label(class: "checkbox-inline") { b.check_box + b.text } %>
<% end %>
</div>
<% end %>
<h4>AND/OR...</h4>
<div class="field form-group">
<%= category_form.label(:title, "Create and Use a New Category:") %><br>
<%= category_form.text_field(:title, class: "form-control") %>
</div>
<% end %>
<div class="actions">
<%= f.submit("Update!", class: "btn btn-block btn-primary btn-lg") %>
</div>
<% end %>
So user is editing a word from their word list, but you want to show checkboxes for all categories for all the words in their word list, checking those categories attached to the word being editing. Is that correct?
It looks like you're missing out the first parameter in #collection_check_boxes, which should be the object you call :category_ids on.
If the object is the user's word_list, then something like:
<%= category_form.collection_check_boxes(current_user.word_list, :category_ids, current_user.word_list.categories.all, :id, :title) do |b| %>
May not be the exact answer--I can't test it--but hope it gives you something to go on.
What's wrong
When you are calling category_form.collection_check_boxes(:category_ids, current_user.word_list.categories.all, :id, :title) within the fields_for :category, you are saying there is a method on #word.category called category_ids which will return the ids of the categories related to #word.category. The checkbox will be checked if there is a matching id both in category_ids results and in current_user.word_list.categories.all.
I don't think there is a #word.category or a #word.category.category_ids. I think there's #word.categories though.
How to fix it
That being said, my instinct tells me that you need to use something like:
<%= form_for(#word) do |f| %>
<div class="field form-group">
<%= f.label(:title, "Word:") %><br>
<%= f.text_field(:title, id: "new_word", required: true, autofocus: true, class: "form-control") %>
</div>
<div class="field form-group">
<%= f.label(:description, "Definition:") %><br>
<%= f.text_area(:description, class: "form-control") %>
</div>
<% if current_user.word_list.categories.count > 0 %>
<div class="field form-group">
<%= f.label(:categories, "Choose from existing Categories:") %><br>
<%= f.collection_check_boxes(:category_ids, current_user.word_list.categories.all, :id, :title) %>
</div>
<% end %>
<div class="actions">
<%= f.submit("Update!", class: "btn btn-block btn-primary btn-lg") %>
</div>
<% end %>
Note that I've moved the collection_check_boxes to the #word's form builder, as you are hoping to build the "categories" value for the current #word. I think this should be a step in the right direction anyway.
Related
messages_controller.rb FILE:
class MessagesController < ApplicationController
def index
#messages = Message.all
#message = Message.new
end
def create
#message = Message.new(message_params)
#messages = Message.all
respond_to do |format|
if #message.save
format.html { redirect_to #message, notice: 'message was successfully created'}
format.js
format.json { render :show, status: :created, location: #message }
end
end
end
private
def message_params
params.require(:message).permit(:body)
end
end
index.html.erb FILE:
<table>
<tbody>
<%= render partial: 'message', collection: #messages %>
</tbody>
</table>
<%= render 'form', message: #message %>
# i remove the code from CREATE.JS.ERB but it results in no change in server (port:3000)#
create.js.erb FILE:
$('table#bands tbody').append("<%= j render #message %>");
_message.html.erb FILE:
<tr>
<td><%= message.body %></td>
</tr>
_form.html.erb FILE:
<%= form_with(model: message) do |f| %>
<div class="col-sm-8">
<%= f.text_area :body, class: "form-control", placeholder: "Message Body" %>
</div>
<div class="btn-group">
<%= f.submit class: 'btn btn-primary' %>
</div>
<% end %>
routes.rb FILE:
Rails.application.routes.draw do
resources :messages
root 'messages#index'
end
schema.rb FILE:
ActiveRecord::Schema.define(version: 2020_07_23_045320) do
create_table "messages", force: :cascade do |t|
t.string "body"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
end
Ruby on rails
I am trying to select a distinct column from a model then display those results in a select drop down. Everything seems to be working except the value of the column doesn't display. It displays some sort of hex value or something. Here is my variable:
#incidenttypes = Incident.select(:incidenttype).distinct.order("incidenttype")
This is the form select:
<%= f.select :incidenttype, #incidenttypes, {include_blank: true}, class: 'form-control' %>
The output looks like this: #<Incident:0x0000000e97a88>
Any thoughts on what I am doing wrong?
UPDATE: Here is the schema for the table:
create_table "incidents", force: :cascade do |t|
t.string "street"
t.string "city"
t.string "zip"
t.integer "dayofweek"
t.date "timeofday"
t.string "state"
t.string "incidenttype"
t.string "drnum"
t.string "weatherevent"
t.string "specialevent"
t.float "latitude"
t.float "longitude"
t.text "comments"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
This is the search form:
<%= form_for(:search, url: incidents_path, method: :get) do |f| %>
<div class="ui-bordered px-4 pt-4 mb-4">
<div class="form-row">
<div class="col-md mb-4">
<label class="form-label">Type</label>
<%= f.select :dayofweek, Incident.dayofweeks.keys, {:include_blank=> 'Any days', :selected => #dayofweek}, class: 'custom-select' %>
</div>
<div class="col-md mb-4">
<label class="form-label">Incident Type</label>
<%= f.select :incidenttype, options_for_select(#incidenttypes), {include_blank: true}, class: 'form-control' %>
</div>
<div class="col-md mb-4">
<label class="form-label">Created date</label>
<%= f.text_field :created_at, class: 'form-control', id: 'tickets-list-created', :autocomplete => :off, value: created_at_from_parameters %>
</div>
<div class="col-md col-xl-2 mb-4">
<label class="form-label d-none d-md-block"> </label>
<button type="submit" class="btn btn-secondary btn-block">Show</button>
</div>
</div>
</div>
<% end %>
Incident controller:
class IncidentsController < ApplicationController
before_action :set_incident, only: [:show, :edit, :update, :destroy]
# GET /incidents
# GET /incidents.json
def index
if params[:search] && params[:search][:created_at].present?
start_date, end_date = params[:search][:created_at].split(' - ')
#incidents = Incident.where(created_at: start_date..end_date).where(dayofweek: params[:search][:dayofweek]).where(incidenttype: params[:search][:incidenttype])
#dayofweek = params[:search][:dayofweek]
#incidenttypes = Incident.order("incidenttype").pluck(:incidenttype, :incidenttype).uniq
else
#incidents = Incident.all
end
end
def map
#incidents = Incident.all
end
# GET /incidents/1
# GET /incidents/1.json
def show
end
# GET /incidents/new
def new
#incident = Incident.new
end
# GET /incidents/1/edit
def edit
end
# POST /incidents
# POST /incidents.json
def create
#incident = Incident.new(incident_params)
respond_to do |format|
if #incident.save
format.html { redirect_to #incident, notice: 'Incident was successfully created.' }
format.json { render :show, status: :created, location: #incident }
else
format.html { render :new }
format.json { render json: #incident.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /incidents/1
# PATCH/PUT /incidents/1.json
def update
respond_to do |format|
if #incident.update(incident_params)
format.html { redirect_to #incident, notice: 'Incident was successfully updated.' }
format.json { render :show, status: :ok, location: #incident }
else
format.html { render :edit }
format.json { render json: #incident.errors, status: :unprocessable_entity }
end
end
end
# DELETE /incidents/1
# DELETE /incidents/1.json
def destroy
#incident.destroy
respond_to do |format|
format.html { redirect_to incidents_url, notice: 'Incident was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_incident
#incident = Incident.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def incident_params
params.require(:incident).permit(:street, :city, :zip, :dayofweek, :timeofday, :state, :incidenttype, :drnum, :weatherevent, :specialevent, :latitude, :longitude, :comments)
end
end
To learn more options_for_select and Select helper
Refactor your query as
#incidenttypes = Incident.order("incidenttype").distinct.pluck(:incidenttype, :id)
Use options_for_select(#incidenttypes)
<%= f.select :incidenttype, options_for_select(#incidenttypes), {include_blank: true}, class: 'form-control' %>
To use is with form follow as
#incidenttypes = Incident.order("incidenttype").distinct.pluck(:incidenttype, :incidenttype)
in optoptions_for_select(array of arrays of options and values, slected_value after submitting form)
<%= f.select :incidenttype, options_for_select(#incidenttypes, params[:incident][:incidenttype]), {include_blank: true}, class: 'form-control' %>
I'm using the the carrierwave gem with my rails app in order to upload multiple images(attachments).
The attachments are being saved inside the item model:
create_table "items", force: :cascade do |t|
t.string "title"
t.string "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.json "attachments"
end
and this the item create action along with the params permitted:
def items_create
#categories = Category.all
#item = Item.new(item_params)
#item.store_id = current_store.id
#item.account_id = current_store.account.id
respond_to do |format|
if #item.save
format.html { redirect_to store_items_show_path_path(current_store), notice: 'Item was successfully created.' }
format.json { render :template => "stores/items/show", status: :created, location: #item }
else
format.html { render :template => "stores/items/new" }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
def item_params
params.require(:item).permit(:title, :description, attachments: [])
end
Now I'm trying to implement How to: Add more files and remove single file when using default multiple file uploads feature from here.
and this is the setup I have:
def create_image
add_more_images(images_params[:attachments])
flash[:error] = "Failed uploading attachments" unless #item.save
redirect_back(fallback_location: root_path)
end
def destroy_image
remove_image_at_index(params[:id].to_i)
flash[:error] = "Failed deleting attachment" unless #item.save
redirect_back(fallback_location: root_path)
end
private
def add_more_images(new_images)
attachments = #item.attachments
attachments += new_images
#item.attachments = attachments
end
def remove_image_at_index(index)
remain_attachments = #item.attachments # copy the array
deleted_attachment = remain_attachments.delete_at(index) # delete the target attachment
deleted_attachment.try(:remove!) # delete attachment from S3
#item.attachments = remain_attachments # re-assign back
#item.remove_attachments! if remain_attachments.empty?
end
def images_params
params.require(:item).permit({attachments: []}) # allow nested params as array
end
routes.rb
match 'stores/items/:item_id/attachments/:id'=> 'stores#destroy_image', :via => :delete, :as => :remove_attachment
post "store/items/:item_id/attachments/:id"=> "stores#create_image", :as => :create_image
I got it to upload a single file with this:
<%= form_for #item, url: create_image_path(#item), method: :post do |form| %>
<div class="form-group">
<label class="btn btn-default">Keep old and add more images<span style="display:none;">
<%= form.file_field :attachments, multiple: true, id: "files" %></span></label>
</div>
<%= form.submit "Upload new images", :class=>"btn btn-default" %>
<% end %>
but can't remove each attachment I loop thru with this:
<h3>Currently uploaded images</h3>
<% #item.attachments.each_with_index do |attachments, index| #grab the index %>
<%= image_tag(attachments.url(:mini)) %>
<%= link_to 'Remove', remove_attachment_path(#item, index), data: { confirm: "Are you sure you want to delete this image?" }, :method => :delete %>
<% end %>
This is the error I'm getting each time i click on remove:
ActiveRecord::RecordNotFound in StoresController#destroy_image
Couldn't find Item with 'id'=2
in this url : https://localhost:3000/stores/items/29/attachments/2
and at this line:
def set_item
#item = Item.find(params[:id])
end
Any ideas on how to fix the remove a single attachment feature??
Thanks in advance!
Ok, so I integrated money-rails gem into my basic calculating app ...
When I try to run it now, I get this error:
ActiveRecord::StatementInvalid in TippiesController#create
and this line underneath it:
PG::NotNullViolation: ERROR: null value in column "tip" violates not-null constraint DETAIL: Failing row contains (37, null, null, 2017-03-10 07:47:36.152504, 2017-03-10 07:47:36.152504, 3300, USD, 10, USD). : INSERT INTO "tippies" ("created_at", "updated_at", "cost_cents", "tip_cents") VALUES ($1, $2, $3, $4) RETURNING "id"" ...
So, I thought this meant that it was generating this because my my model stipulates validates :tip, presence: true ... so I commented it out, but still receive this error.
Not sure how to sort it out. Any help in figuring it out would be appreciated.
Current Model
class Tippy < ApplicationRecord
validates :tip_cents, presence: true
validates :cost_cents, presence: true
monetize :tip_cents
monetize :cost_cents
TIP_CHOICES = { "10%" => ".10", "20%" => ".20", "30%" => ".30", "40%" => ".40", "50%" => ".50",
"60%" => ".60", "70%" => ".70", "80%" => ".80", "90%" => ".90" }
def calculation_of_total_cost
cost_cents + (tip_cents * cost_cents)
end
end
Current Controller
class TippiesController < ApplicationController
before_action :set_tippy, only: [:show, :edit, :update, :destroy]
# GET /tippies
# GET /tippies.json
def index
#tippies = Tippy.all
end
# GET /tippies/1
# GET /tippies/1.json
def show
##calculation_of_total_cost
end
# GET /tippies/new
def new
#tippy = Tippy.new
end
# GET /tippies/1/edit
def edit
end
# POST /tippies
# POST /tippies.json
def create
#tippy = Tippy.new(tippy_params)
respond_to do |format|
if #tippy.save
format.html { redirect_to #tippy, notice: 'Tippy was successfully created.' }
format.json { render :show, status: :created, location: #tippy }
else
format.html { render :new }
format.json { render json: #tippy.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /tippies/1
# PATCH/PUT /tippies/1.json
def update
respond_to do |format|
if #tippy.update(tippy_params)
format.html { redirect_to #tippy, notice: 'Tippy was successfully updated.' }
format.json { render :show, status: :ok, location: #tippy }
else
format.html { render :edit }
format.json { render json: #tippy.errors, status: :unprocessable_entity }
end
end
end
# DELETE /tippies/1
# DELETE /tippies/1.json
def destroy
#tippy.destroy
respond_to do |format|
format.html { redirect_to tippies_url, notice: 'Tippy was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_tippy
#tippy = Tippy.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def tippy_params
params.require(:tippy).permit(:tip, :cost, :tip_cents, :tip_currency, :cost_cents, :cost_currency)
end
end
Migrated Monetize File
class MonetizeTippy < ActiveRecord::Migration[5.0]
def change
add_monetize :tippies, :cost
add_monetize :tippies, :tip
end
end
Schema
ActiveRecord::Schema.define(version: 20170310070749) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "tippies", force: :cascade do |t|
t.float "tip", null: false
t.decimal "cost", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "cost_cents", default: 0, null: false
t.string "cost_currency", default: "USD", null: false
t.integer "tip_cents", default: 0, null: false
t.string "tip_currency", default: "USD", null: false
end
end
Show.html.erb (this page pops up after the home page which is new action
<br/><br/>
<h1 class="text-center">Your Total Cost</h1>
<br/><br />
<table class="table table-striped">
<tr>
<td>
Cost of Your Meal:
</td>
<td>
<%= humanized_money_with_symbol #tippy.cost_cents %>
</td>
</tr>
<tr>
<td>
Tip You Picked:
</td>
<td>
<%= number_to_percentage(#tippy.tip_cents * 100, format: "%n%", precision: 0) %>
</td>
</tr>
<tr>
<td>
The Total Cost:
</td>
<td>
<%= humanized_money_with_symbol #tippy.calculation_of_total_cost %>
</td>
</tr>
</table>
new.html.erb
<br /><br />
<h1 class="text-center">Calculate Your Tip!</h1>
<%= render 'form', tippy: #tippy %>
_form.html.erb
<%= form_for(tippy, :html => {'class' => "form-horizontal"}) do |f| %>
<% if tippy.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(tippy.errors.count, "error") %> prohibited this tippy from being saved:</h2>
<ul>
<% tippy.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field form-group">
<%= f.label :cost_of_your_meal, class: "control-label" %>
<%= f.text_field :cost, class: "form-control" %>
</div>
<div class="field form-group">
<%= f.label :pick_your_tip, class: "control-label" %>
<%= f.select(:tip, Tippy::TIP_CHOICES, class: "form-control") %>
</div>
<div class="actions">
<%= f.submit class: "btn btn-primary btn-lg btn-block" %>
</div>
<% end %>
This error comes from the database, not Rails.
In the migration where the tip column gets added, it will look something like this:
t.float :tip, null: false
This translates to a NOT NULL constraint in your SQL schema.
A database constraint and a validation do not serve the same purpose: the former is for definitely preventing incorrect data from being persisted, whereas the latter is an easy way to communicate errors back to your users through the automatically added ActiveModel::Errors object.
To solve your problem you have to add another migration and make tip nullable again (and maybe provide an appropriate default value):
change_column :tippies, :tip, :float, null: true, default: 0
I want to build a form for entries, where a couple of contest_questions are predicted by the user.
That means I dynamically wants to fetch the contest_questions related to the tipster-contest and then letting the user register an entry with predictions for each of those questions.
How can I do this? As of now I the fields are not showing up since <%= f.fields_for :predictions do |prediction| %>does not execute within the contest_question block and if I change it to contest_question.fields_for ...I get a
undefined method `fields_for' for #
tipster_contest.rb
class TipsterContest < ActiveRecord::Base
belongs_to :bookmaker
belongs_to :game
has_many :entries
has_many :contest_questions
extend FriendlyId
friendly_id :name, use: [:slugged, :history]
enum status: { "Scheduled" => 0, "Open" => 1, "Closed" => 2, "Graded" => 3 }
scope :published?, -> { where(published: :true) }
end
entry.rb
class Entry < ActiveRecord::Base
belongs_to :user
belongs_to :tipster_contest
has_many :predictions
accepts_nested_attributes_for :predictions
end
contest_question.rb
class ContestQuestion < ActiveRecord::Base
belongs_to :tipster_contest
has_many :predictions
end
prediction.rb
class Prediction < ActiveRecord::Base
belongs_to :entry
has_one :contest_question
end
_form.html.erb
<%= form_for(#entry) do |f| %>
<% #contest.contest_questions.order(name: :asc).each do |contest_question| %>
<%= f.fields_for :predictions do |prediction| %>
<div class="field">
<%= prediction.label contest_question.name %>
<%= prediction.select(:prediction, #contest.teams) %>
</div>
<div class="field">
<%= prediction.label :wager_amount %>
<%= prediction.input :wager_amount %>
</div>
<% end %>
<% end %>
<div class="actions">
<%= f.submit "Save entry", :class => "btn btn-success" %> <%= link_to 'Back', bets_path, :class => "btn btn-danger" %>
</div>
<% end %>
And the relevant part of my schema.rb if that would be needed:
create_table "contest_questions", force: true do |t|
t.integer "tipster_contest_id"
t.string "name"
t.string "result"
t.integer "min_wager"
t.integer "max_wager"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "entries", force: true do |t|
t.integer "user_id"
t.integer "tipster_contest_id"
t.integer "bankroll"
t.integer "won"
t.string "currency"
t.boolean "entry_valid"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "predictions", force: true do |t|
t.integer "entry_id"
t.integer "contest_question_id"
t.string "prediction"
t.integer "wager_amount"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "tipster_contests", force: true do |t|
t.integer "bookmaker_id"
t.integer "game_id"
t.string "name"
t.string "tournament"
t.integer "status", default: 0
t.text "rules"
t.integer "prizepool"
t.string "currency"
t.text "payout_structure"
t.integer "tipster_wallet"
t.string "teams", default: [], array: true
t.datetime "registration_open"
t.datetime "registration_close"
t.boolean "published", default: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "logo"
t.text "description"
t.string "slug"
end
add_index "tipster_contests", ["slug"], name: "index_tipster_contests_on_slug", using: :btree
Managed to figure it out and at least get it working. I don't know if the solution is any good in terms of the rails standard. Anyway here it is:
_form.html.erb
<% title("#{#contest.name}") %>
<% description("Join #{#contest.name} and use your eSports knowledge to win extra cash or in-game skins for games like CS:GO, Dota 2, etc.") %>
<p id="notice"><%= notice %></p>
<%= form_for(#entry) do |f| %>
<% if #entry.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#entry.errors.count, "error") %> prohibited this entry from being saved:</h2>
<ul>
<% #entry.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.hidden_field 'tipster_contest_id', :value => #contest.id %>
<div class="t_container">
<div class="t_row">
<div class="t_left"><b>Placement</b></div>
<div class="t_middle"><b>Team</b></div>
<div class="t_right"><b>Wager Amount</b></div>
</div>
<%= f.fields_for :predictions, #predictions do |p| %>
<div class="t_row">
<div class="t_left"><%= #entry.contest_questions.order(:name => :asc)[p.index].name %></div>
<div class="t_middle"><%= p.select :prediction, #contest.teams %></div>
<div class="t_right"><%= p.text_field :wager_amount, label: "Wager amount" %></div>
<%= p.hidden_field 'contest_question_id', :value => #entry.contest_questions.order(:name => :asc)[p.index].id %>
</div>
<% end %>
</div>
</br>
<div class="actions">
<%= f.submit "Save entry", :class => "btn btn-success" %> <%= link_to 'Back', bets_path, :class => "btn btn-danger" %>
</div>
<% end %>
entries_controller.rb
class EntriesController < ApplicationController
before_action :set_entry, only: [:show, :edit, :update, :destroy]
# GET /entries
# GET /entries.json
def index
#entries_open = current_user.entries.includes(:tipster_contest).where(:tipster_contests => {status: 1})
#entries_closed = current_user.entries.includes(:tipster_contest).where(:tipster_contests => {status: 2})
#entries_graded = current_user.entries.includes(:tipster_contest).where(:tipster_contests => {status: 3})
#contests = TipsterContest.where(status: 0..1).includes(:game)
end
# GET /entries/1
# GET /entries/1.json
def show
end
# GET /entries/new
def new
if user_signed_in?
begin
#contest = TipsterContest.friendly.find(params[:contest])
redirect_to tipster_contests_path(#contest), notice: "#{#contest.name} does not accept entries at this point. Registration opens at: #{#contest.registration_open} and closes at: #{#contest.registration_close}." and return true unless #contest.status == "Open"
#entry = Entry.new(tipster_contest: #contest)
#predictions = []
#contest.contest_questions.order(name: :asc).each do |cq|
#predictions << Prediction.new(entry: #entry.id, contest_question_id: cq.id)
end
rescue
redirect_to tipster_contests_path, notice: 'Could not find a tipster-contest with that ID'
end
else
redirect_to new_user_registration_path, notice: 'You need to sign up to enter the tipster contest!'
end
end
# GET /entries/1/edit
def edit
if user_signed_in?
#contest = #entry.tipster_contest
#predictions = #entry.predictions
redirect_to tipster_contests_path, notice: 'This is not your entry to edit' and return true unless #entry.user == current_user
else
redirect_to new_user_registration_path, notice: 'You need to sign up to enter or edit a tipster contest entry!'
end
end
# POST /entries
# POST /entries.json
def create
#entry = Entry.new(entry_params)
#predictions = #entry.predictions
#contest = #entry.tipster_contest
#entry.user = current_user
#entry.won = 0
#entry.bankroll = #contest.tipster_wallet
#entry.currency = #contest.currency
respond_to do |format|
if #entry.save
format.html { redirect_to #entry, notice: 'Entry was successfully created.' }
format.json { render :show, status: :created, location: #entry }
else
format.html { render :new }
format.json { render json: #entry.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /entries/1
# PATCH/PUT /entries/1.json
def update
#contest = #entry.tipster_contest
##predictions = #entry.predictions
respond_to do |format|
if #entry.update(entry_params)
format.html { redirect_to #entry, notice: 'Entry was successfully updated.' }
format.json { render :show, status: :ok, location: #entry }
else
format.html { render :edit }
format.json { render json: #entry.errors, status: :unprocessable_entity }
end
end
end
# DELETE /entries/1
# DELETE /entries/1.json
def destroy
#entry.destroy
respond_to do |format|
format.html { redirect_to entries_url, notice: 'Entry was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_entry
#entry = Entry.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def entry_params
params[:entry].permit(:id, :tipster_contest_id, predictions_attributes: [:prediction, :wager_amount, :contest_question_id, :id])
end
end