I've been trying Ruby on Rails for a few months now, re-engineering a desktop application. This application has a form for updating take-off and landing points, whose coordinates must be confirmed. Well, I tried validates_confirmation_of method however it works only with String attributes, and I have Integer and Decimal types. Hence I created an alternative method by borrowing an excerpt from the helper (confirmation.rb), which is working fine. But I wonder if there is another way to do it or whether this solution can be improved. Below follows a compact version of the source files. Thanks in advance!
Migration:
class CreateWaypoints < ActiveRecord::Migration[5.0]
def change
create_table :waypoints do |t|
t.string :name
t.integer :latitude_in_degrees
t.integer :latitude_in_minutes
t.decimal :latitude_in_seconds, precision: 5, scale: 2
t.timestamps
end
add_index :waypoints, [:name], unique: true
end
end
View:
<%= simple_form_for(#waypoint) do |f| %>
<%= f.error_notification %>
<div class="form-inline">
<%= f.input :name %>
</div>
<div class="form-inline">
<%= f.input :latitude_in_degrees %>
<%= f.input :latitude_in_minutes %>
<%= f.input :latitude_in_seconds %>
</div>
<div class="form-inline">
<%= f.input :latitude_in_degrees_confirmation, as: :numeric, label: false %>
<%= f.input :latitude_in_minutes_confirmation, as: :numeric, label: false %>
<%= f.input :latitude_in_seconds_confirmation, as: :numeric, label: false %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
Model:
class Waypoint < ApplicationRecord
...
validates_each :latitude_in_degrees, :latitude_in_minutes, :latitude_in_seconds do |record, attribute, value|
if record.send("#{attribute}_changed?")
confirmed = record.send("#{attribute}_confirmation")
if confirmed.to_s != value.to_s
human_attribute_name = record.class.human_attribute_name(attribute)
record.errors.add(:"#{attribute}_confirmation", "Doesn't match #{human_attribute_name}")
end
end
end
end
Related
I have my parameters whitelisted and when I look at the output of party_params I see that they are permitted but when I got to save the instance into the database it gives me a rollback transaction in the console. I've tried just create, create then save, new then save. Is there something I am missing?
#controller
class PartiesController < ApplicationController
def new
#party = Party.new
end
def create
#party = Party.create(party_params)
redirect_to party_path(#party)
end
private
def party_params
params.require(:party).permit(
:name,
:trainer_id,
:pokemon1_id
)
end
end
#model
class Party < ApplicationRecord
belongs_to :trainer
belongs_to :pokemon
validates :name, presence: true
end
#view
<h1>Create a New Pokemon Party</h1>
<p>Select 6 Pokemon</p>
<%= form_for(#party) do |f| %>
<label>Party Name:</label>
<%= f.text_field :name %><br>
<label>Trainer Name:</label>
<%= collection_select(:party, :trainer_id, Trainer.order(:id), :id, :name, include_blank: true) %><br>
<label>Pokemon:</label>
<%= collection_select(:party, :pokemon1_id, Pokemon.order(:id), :id, :nickname, include_blank: false) %><br>
<%= f.submit %>
<% end %>
#schema
create_table "parties", force: :cascade do |t|
t.string "name"
t.integer "pokemon1_id"
t.integer "trainer_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
Lets start with the models. If you want a party to be able to include multiple pokemon you need to place the foreign key in the other model:
class Party < ApplicationRecord
belongs_to :trainer
has_many :pokemons # references the pokemons.party_id column
end
class Pokemon < ApplicationRecord
belongs_to :party # pokemons needs a `party_id` column
end
class AddPartyToPokemons < ActiveRecord::Migration
def change
add_reference :pokemons, :party, null: false, foreign_key: true
remove_column :parties, :pokemon1_id
end
end
This is very simplefied and assumes that Pokemon is an individual Pokemon and not the entire species and can only belong to a single party. Otherwise you need a many-to-many assocation with a join table/model.
In your controller you need to check if creating the record was actually successful and respond accordingly:
class PartiesController < ApplicationController
def new
#party = Party.new
end
def create
#party = Party.new(party_params)
if #party.save
redirect_to #party
else
render :new
end
end
private
def party_params
params.require(:party).permit(
:name,
:trainer_id,
pokemon_ids: []
)
end
end
If the user input is invalid this will render the app/parties/new.html.erb view and respond with it.
While you could use pry or byebug to step into the controller and check the errors you want to display the validation errors to the user in the view anyways so that they know what to actually do to correct the form:
<h1>Create a New Pokemon Party</h1>
<p>Select 6 Pokemon</p>
<%= form_for(#party) do |f| %>
<% if #party.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#party.errors.count, "error") %> prohibited this party from being saved:</h2>
<ul>
<% #article.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<%
# group labels and inputs in an element instead of abusing BR tags
# this lets you style the content with CSS
-%>
<div class="field">
<%# use f.label as it sets the `for=` attribute which is important for screen readers -%>
<%= f.label :name, 'Party Name:' %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :trainer_id, 'Trainer Name:' %>
<%# call the helper on the form builder to bind the input to the model -%>
<%= f.collection_select(:trainer_id, Trainer.order(:id), :id, :name, include_blank: true) %>
</div>
<div class="field">
<%= f.label :pokemon_ids, 'Pokemon:' %>
<%# call the helper on the form builder to bind the input to the model -%>
<%= f.collection_select(:pokemon_ids, Pokemon.order(:id), :id, :nickname, include_blank: false, multiple: true) %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Note f.collection_select(:pokemon_ids, ...). This is a special setter/getter is generated by has_many :pokemons.
I am building an application to manage debts and am trying to create three objects using the same form.
The models are;
Debt (the amount owed)
class Debt < ActiveRecord::Base
belongs_to :user
belongs_to :creditor
belongs_to :debtor
accepts_nested_attributes_for :debtor, :creditor
end
Debtor (The person who owes)
class Debtor < ActiveRecord::Base
belongs_to :user
has_many :debts
end
Creditor (The person who is owed)
class Creditor < ActiveRecord::Base
belongs_to :user
has_many :debts
end
I am using the new debt form and would like to show fields for debtor and creditor. So when a new debt is created, so is the associated debtor and creditor.
I have viewed the documentation however, cannot get the fields for debtor or creditor to display on the form.
This is the form
<%= simple_form_for(#debt) do |f| %>
<%= f.error_notification %>
<!-- Debt fields -->
<div class="form-inputs">
<%= f.association :user %>
<%= f.input :amount %>
<%= f.input :commission %>
<%= f.input :invoice_issued %>
<%= f.input :invoice_date %>
<%= f.input :status %>
<%= f.input :details %>
</div>
<!-- Debtor Fields -->
<ul>
<%= f.fields_for :debtor do |debtor_form| %>
<li>
<%= debtor_form.association :user %>
<%= debtor_form.input :business_name %>
<%= debtor_form.input :abn %>
<%= debtor_form.input :first_name %>
<%= debtor_form.input :last_name %>
<%= debtor_form.input :email %>
<%= debtor_form.input :mobile_number %>
<%= debtor_form.input :phone_number %>
</li>
<% end %>
</ul>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
Any help is appreciated.
EDIT
create_table "debts", force: :cascade do |t|
t.integer "user_id"
t.float "amount"
t.float "commission"
t.boolean "invoice_issued"
t.date "invoice_date"
t.string "status"
t.text "details"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
You need to build your debt's debtor or creditor in your controller:
#controller
def new
#debt = Debt.new
#debt.build_debtor
#bebt.build_creditor
end
I have a Word model and a Category model.
Words has_and_belongs_to_many Categories.
Categories has_and_belongs_to_many Words.
Now, I have everything setup and running in the console, for example you can do:
Word.create(title: "Testicles")
testicles = Word.first
Category.create(title: "Genitalia")
genitalia = Category.first
testicles.categories << genitalia
testicles.categories
=> "genitalia"
Now I can get this up and running using forms in the views too, but only if I have separately created the Category in its own form on a separate page, and same with the Word. Then in the Word show view I can create a form to assign the category to it.
HOWEVER... what I really want to do is to do all this at the same time when I create the Word i.e. on the 'new word' view.
I'm having big problems working out how to do this. I think I'm right in saying that I can only have one form and one submit in that view, so I think I somehow have to send everything from that form to, say, the WordsController, and work some magic in there, but exactly what to do here is giving me big headaches. Can anyone help?
I haven't created a User model or setup authentication yet, so there are no obstacles in that respect.
models/word.rb:
class Word < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :categories
validates :title, presence: true
end
models/category.rb:
class Category < ActiveRecord::Base
has_and_belongs_to_many :words
validates :title, presence: true
end
schema.rb:
ActiveRecord::Schema.define(version: 20150529144121) do
create_table "categories", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
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 "words", force: :cascade do |t|
t.string "title"
t.text "description"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "words", ["user_id"], name: "index_words_on_user_id"
end
After experimenting with various form_tag wizardry (and failing badly), at the moment I'm using this form:
<%= form_for(#word) do |f| %>
<% if #word.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#word.errors.count, "error") %> prohibited this word from being saved:</h2>
<ul>
<% #word.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title, 'Word' %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description, 'Definition' %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.label :categories, 'Category' %><br>
<%= f.text_field :categories %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
In the view, the form has:
#<Category::ActiveRecord_Associations_CollectionProxy:0x007f16dd834820>
in the 'Category' box, but this can be cleared and your own category inputted. When submitting the form with all the fields filled in, I get a NoMethodError.
So basically you want to create both a word and a category at the same time, and link them. If the classical solutions (for example using the nested_form gem) don't do exactly what you want, you can try this approach.
You really want a custom view/controller for this and do the business in your controller as you suggest. I suggest that instead you used a simple form_tag where you can add some fieldsets.
View
<%= form_tag your_custom_route_path, :html => {:class => "form-horizontal"} do |form| %>
<%= fields_for :word, #word do |f| %>
<%= f.text_field :title %>
<% end %>
<%= fields_for :category, #category do |f| %>
<%= f.text_field :title %>
<% end %>
<% end %>
routes
post '/yourRoute', to: 'your_controller#your_action_create', as: 'your_custom_route'
Controller featuring some actions
class YourController < ApplicationController
# new
def your_action_new
#word = Word.new
#category = Category.new
end
# Post
def your_action_create
#word = Word.new(word_params)
#category = Category.new(category_params)
if #word.save and #category.save
#word.categories << #category
#word.save
end
end
private
def words_params
params.require(:word).permit(:title, ...)
end
def category_params
params.require(:category).permit(:title...)
end
Thanks to Cyril DD's help, I've expanded on that and created a form and custom controller that will create a new Word with a new Category assigned to it and/or the user can assign existing Categories to that new Word.
_form.html.erb:
<%= form_tag(controller: "new_word", action: "create_word_and_category", method: "post") do %>
<% if #word.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#word.errors.count, "error") %> prohibited this category from being saved:</h2>
<ul>
<% #word.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= fields_for :word, #word do |word_form| %>
<div class="field">
<%= word_form.label(:title, "Word:") %><br>
<%= word_form.text_field(:title, id: "new_word", required: true) %>
</div>
<div class="field">
<%= word_form.label(:description, "Definition:") %><br>
<%= word_form.text_area(:description) %>
</div>
<% end %>
<%= fields_for :category, #category do |category_form| %>
<% if Category.count > 0 %>
<div class="field">
<%= category_form.label(:title, "Choose from existing Categories:") %><br>
<%= category_form.collection_check_boxes(:category_ids, Category.all, :id, :title) %>
</div>
<% end %>
<h4>AND/OR...</h4>
<div class="field">
<%= category_form.label(:title, "Create and Use a New Category:") %><br>
<%= category_form.text_field(:title) %>
</div>
<% end %>
<div class="actions">
<%= submit_tag("Create") %>
</div>
<% end %>
new_word_controller.rb (it's not completely finished yet, but it 'does what it says on the tin'):
class NewWordController < ApplicationController
def create_word_and_category
#word = Word.new(word_params)
if #word.save
(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
if category_params.include?(:title) && ((params["category"])["title"]) != ""
#word.categories << Category.new(title: (params["category"])["title"])
end
end
if #word.save
redirect_to words_path, notice: 'Word was successfully created.'
else
redirect_to new_word_path, notice: 'A valid word was not submitted.'
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, :user_id)
end
def category_params
params.require(:category).permit(:title, :category_ids, :category_id)
end
end
In my routes.rb:
post 'create_word_and_category' => 'new_word#create_word_and_category'
I'm having going to place that controller code into the old words_controller, because I don't see a need to have it in a separate controller like this. If anyone has any thoughts/critique, great. And if anyone can help me write an rspec controller test for this controller code, even better! I'm having trouble with that.
I'm having problems changing the data type of a particular model's attribute (i.e. money). Money was an integer -- I'm trying to use a migration to change it to a decimal, with a precision of 5 and a scale of 2. Upon doing the migration, everything looks OK in console (i.e. it appears the migration worked), but when I go to change a value to a decimal in the web app, I get an 'Invalid value' message. The error appears to be taking place on the client-side for some reason (it's a js popup)? I didn't include any client-side validations though. Here are the steps I take:
First, I generate the migration:
>rails generate migration change_data_type_for_user_money
Then, I edit the migration. Here's what it looks like:
class ChangeDataTypeForUserMoney < ActiveRecord::Migration
def self.up
change_table :users do |t|
t.change :money, :decimal, :precision => 5, :scale => 2
end
end
def self.down
change_table :users do |t|
t.change :money, :decimal, :precision => 5, :scale => 2
end
end
end
Then I do a 'rake db:migrate'
What is odd is that zero's following the decimal point appear and are fine (e.g. 100.00 is OK, but 100.50 is when I get the error)
Also, here's the model:
class User < ActiveRecord::Base
attr_accessible :money, :name
validates :money, :name, :presence => true
validates :money, :numericality => true
end
And the _from view:
<%= form_for(#user) do |f| %>
<% if #user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :money %><br />
$<%= f.number_field :money %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Any thoughts? Thanks in advance for your help!
Not sure, but try:
class ChangeDataTypeForUserMoney < ActiveRecord::Migration
def change
change_table :users do |t|
t.decimal :money, :precision => 5, :scale => 2
end
end
end
EDIT: Turned out the issue was not with migration at all, but the validator. The solution was to change: <%= f.number_field :money %> to <%= f.text_field :money %> as per my comment below.
I'm trying to insert a drop down menu values to a table named units and that drop values are coming from another model named properties.
Here is the schema of the units.
create_table "units", :force => true do |t|
t.string "number"
t.decimal "monthly_rent"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "property_id"
end
on my view/units/new.html.erb I have this.
<% form_for #unit do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :property_id %>
<br/>
<%= select (:unit, :property_id, Property.all.collect {|property| [property.name,property.id,]}) %>
</p>
<p>
<%= f.label :number %>
<br/>
<%= f.text_field :number %>
</p>
<p>
<%= f.label :monthly_rent %>
<br/>
<%= f.text_field :monthly_rent %>
</p>
<p>
<%= f.submit "Submit" %>
</p>
<% end %>
And here is my controller method
def create
#unit = Unit.new(params[:unit])
# #unit.property_id = 1
if #unit.save
flash[:notice] = "Successfully created unit."
redirect_to #unit
else
render :action => 'new'
end
end
Only number and monthy_rent getting inserted to the table but the property_id doesn't come. Can some body help me on this please? Thanks
The possible issue I can see from what you've shown us is the errant comma in your select helper. Try rewriting it like this (also using the form helper method):
<%= f.select :property_id, Property.all.collect {|property| [ property.name, property.id ] } %>
And yes, the output of the create params to your log would help immensely.
I figured it out, sorry guys it was my mistake. It was not working because, property_id is not added to the attr_accessible, once I added that it worked. but I still don't know the exact issue yet.
class Unit < ActiveRecord::Base
belongs_to :property
attr_accessible :number, :monthly_rent, :property_id
end