Don't use a reference's id when creating an object - ruby-on-rails

I have two objects in my app: user which has a unique parameter email_address and node which has a name and belongs to a user.
Here is my two objects in the DB, Node:
class CreateNodes < ActiveRecord::Migration
def change
create_table :nodes do |t|
t.string :name
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
And User:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :email_address, unique: true, index: true
t.timestamps null: false
end
end
end
My form to add a node looks like this:
<%= form_for #node do |f| %>
<%= f.text_field :name, :class => "form-control" %>
<%= f.number_field :user_id, :class => "form-control" %>
<%= f.submit 'Add the node' %>
<% end %>
However, I would like to use the user's email_address parameter instead of his/her id for convenience. I already did the changes in my controller to find the id linked to the email address but my new form returns an error, here it is:
<%= form_for #node do |f| %>
<%= f.text_field :name, :class => "form-control" %>
<%= f.email_field :user_email_address, :class => "form-control" %>
<%= f.submit 'Add the node' %>
<% end %>
The error:
undefined method `user_email_address' for #<Node id: nil, name: nil, user_id: nil>
The problem returned is clear but I don't know how to add this method and still follow the best practices for RoR development, any suggestions?

When you have the below line :
<%= f.email_field :user_email_address, :class => "form-control" %>
The f object, which is a Node object, calling the method user_email_address on itself. As there is no such method, you are getting the error.
Add a method inside the Node model:
def user_email_address
self.user.email_address
end
After adding the above method, below will work :
<%= f.email_field :user_email_address, :class => "form-control" %>

Related

undefined methods form [NEWBE]

So I have a form and corresponding classes. I got an error:
undefined method `start' for #<Klass id: nil, name: nil, teacher: nil, day: nil>
for line: <%= f.text_field :start, class: 'form-control' %>
and (when I'm trying to delete the above one) <%= f.text_field :duration, class: 'form-control' %>
removing both fields makes my website ok.
My whole form code:
<%= form_for #klass do | f | %>
<div class = “form-group”>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :teacher %>
<%= f.text_field :teacher, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :start %>
<%= f.text_field :start, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :duration %>
<%= f.text_field :duration, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :day %>
<%= f.select :day, ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'] %>
</div>
<%= f.submit 'Submit', class: 'btn btn-default' %>
</div>
<% end %>
model (changing integer to string doesn't make any difference, but that is the only difference betweenthese two fields and the rest of the form that I can see) :
class Klass < ActiveRecord::Base
validates :name, presence: true
validates :teacher, presence: true
validates :day, presence: true
validates :start, presence: true
validates :duration, presence: true, numericality: { only_integer: true }
end
database file:
class CreateKlasses < ActiveRecord::Migration[5.0]
def change
create_table :klasses do |t|
t.string :name
t.string :teacher
t.string :day
t.integer :start
t.integer :duration
end
end
end
and controller:
class KlassesController < ApplicationController
def new
#klass = Klass.new
end
end
Looks for me like I missed to declare these two form fields but where else I can look for it?
1- rake db:rollback
make sure you have these fields added in migration file
class CreateKlasses < ActiveRecord::Migration[5.0]
def change
create_table :klasses do |t|
t.string :name
t.string :teacher
t.string :day
t.integer :start
t.integer :duration
end
end
end
2- rake db:migrate
now reload rails console
reload!
or just close rails console and open rails console again.
check again Klass.new if it has all field that you added in migration. if those field exists then restart server and thats it.

Rails - Confirmation of numeric attributes

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

undefined local variable or method error-attempting to link a comment to a blog

I am working on a Rails 4 project and I currently have a comment section that is tied to a group section-the group is almost like a blog. I need the group id number to be linked to a new comment. I do not want the user to select the group id but instead have it automatically show up. I currently have this in my comment form, my problem is coming in form the last form-group with the Group Id:
<div class="form-group">
<%= form.label :author %>
<%= form.text_field :author, autofocus: true, class: "form-control",
placeholder: "Author's Name" %>
</div>
<div class="form-group">
<%= form.label :comment %>
<%= form.text_field :comment, class: "form-control", placeholder: "Write
your hearts content" %>
</div>
<div class="hide">
<%= form.number_field :user_id, value: current_user.id %>
</div>
<div class="form-group">
<%= form.number_field :group_id, value: group.id %>
</div>
What I want to do is have something like the :user_id, value: current_user.id work with each group. The user id line works fine and I have no problems with it. It is only that group line that I receive the following error: undefined local variable or method `group' for #<#:0x007f873a161fc0>
My schema looks like the following:
create_table "comments", force: :cascade do |t|
t.string "author"
t.text "comment"
t.integer "user_id"
t.integer "group_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "groups", force: :cascade do |t|
t.string "topic"
t.integer "user_id"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.float "latitude"
t.float "longitude"
t.string "address"
t.string "city"
t.string "state"
end
Finally, my comments controller looks like the following:
class CommentsController < ApplicationController
def new
#comment = Comment.new
end
def create
#comment = Comment.new(comment_params)
if #comment.save
redirect_to #group
else
render 'new'
end
end
def edit
end
def update
end
private
def comment_params
params.require(:comment).permit(:author, :comment, :user_id, :group_id)
end
end
Please let me know if any more information is needed. Thank you for the help!
Here are the models for groups and Comments:
Comment:
class Comment < ActiveRecord::Base
belongs_to :group
end
Group:
class Group < ActiveRecord::Base
has_many :collections
has_many :comments
has_many :users, :through => :collections
validates :topic, presence: true
validates :description, presence: true, length: { minimum: 10 }
geocoded_by :address
after_validation :geocode
end
Without specifying the :value option in a form_for method, Rails automatically infers the value from the column name, so leaving it out like below should work for your specific use case, otherwise instead of referring to group, use #comment.group_id:
<div class="form-group">
<%= form.label :author %>
<%= form.text_field :author, autofocus: true, class: "form-control",
placeholder: "Author's Name" %>
</div>
<div class="form-group">
<%= form.label :comment %>
<%= form.text_field :comment, class: "form-control", placeholder: "Write
your hearts content" %>
</div>
<div class="hide">
<%= form.number_field :user_id, value: current_user.id %>
</div>
<div class="form-group">
<%= form.number_field :group_id %>
</div>
However, I'm not totally sure whether you're approaching a few things right.
Some immediate changes, I'd probably propose would be:
Use nested routes:
# routes.rb
resources :groups do
resources :comments
end
In your CommentsController, you could now fetch the group_id from the route:
def create
group = Group.find(params[:group_id])
#comment = current_user.comments.new(comment_params)
#comment.group = group
if #comment.save
redirect_to group
else
render 'new'
end
end
private
def comment_params
params.require(:comment).permit(:author, :comment)
end
This way, in your view, you could easily do:
<div class="form-group">
<%= form.label :author %>
<%= form.text_field :author, autofocus: true, class: "form-control",
placeholder: "Author's Name" %>
</div>
<div class="form-group">
<%= form.label :comment %>
<%= form.text_field :comment, class: "form-control", placeholder: "Write
your hearts content" %>
</div>
Note: :user_id and :group_id have been removed from the form fields because those mappings are now done on the controller level.
Read more about nested routes and nested resources here
UPDATE
For a new comment, the group_id would be nil because it has not been associated to it yet(one of the benefits of nested resources), however if you know beforehand what the group might be, you might want to try:
#comment = group.comments.build
and instead use the value of this #comment.
When you create a comment do you know which group it belongs to:?
I think the issue is with this line
<%= form.number_field :group_id, value: group.id %>
So you are assigning an ID but your group is not initialized. Can you do something like this inside new method
def new
#group = Group.find(...) # load a group here and assign below
#comment = Comment.new(group: #group)
end
You could also refactor so when you create a comment you go to tested route like /groups/ID/comments and then the group ID will be inside params
Hope it helps
if you know which group a comment should belong to then you could do something like this
class CommentsController < ApplicationController
def new
#group = Group.find(...)
#comment = Comment.new
end
def create
comment = Comment.create(comments_params)
if comment.save
redirect_to comments_path
else
redirect_to :new
end
end
private
def comments_params
params.require(:comment).permit(:body, :group_id)
end
end
and inside your form you could have your comment linked to that group
<%= f.number_field :group_id, value: #group.id %>

rails 4 mass assignment error with protected_attributes gem

I'm trying to add a user profile sub module to a user module but having some problems.
Routes:
resources :users do
resources :userprofiles
end
userprofiles_controller.rb:
class UserprofilesController < ApplicationController
def edit
#user = current_user
#user.UserProfile ||= UserProfile.new
#userprofile = #user.UserProfile
end
def update
#user = current_user
#user.UserProfile ||= UserProfile.new
#userprofile = #user.UserProfile
if #userprofile.update_attributes(:userprofile => params[:userprofile])
redirect_to #user
flash[:notice] = "Changes saved."
else
render 'edit'
flash[:notice] = "Error."
end
end
end
user_profile.rb:
class UserProfile < ActiveRecord::Base
attr_accessible :first_name, :last_name, :summary
belongs_to :user
end
Error:
Can't mass-assign protected attributes for UserProfile: userprofile
Line:
if #userprofile.update_attributes(:userprofile => params[:userprofile])
EDIT
Form:
<%= form_for([#user, #userprofile], url: user_userprofile_path(#user, #userprofile)) do |form| %>
<%= form.label :first_name %>
<%= form.text_field :first_name %>
<%= form.label :last_name %>
<%= form.text_field :last_name %>
<%= form.label :summary %>
<%= form.text_area :summary %>
<%= form.submit "Update", class: "btn btn-block btn-primary" %>
<% end %>
Table:
create_table "user_profiles", force: true do |t|
t.string "last_name"
t.string "first_name"
t.text "summary"
t.integer "user_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
You just want
#userprofile.update_attributes(params[:userprofile])
That's a hash with keys :first_name, :last_name, and :summary, which are allowed attributes. When you try to update :userprofile => params[:userprofile], the model checks to see if the key :userprofile is allowed - and it isn't.
I also had this problem. The issue is that you still have attr_accessible in your model controller. Since you don't need them anymore with Rails 4 remove them, add your strong parameters to the controller, and you'll be able to mass-assign without issue.

Using Rails models with accepts_nested_attributes_for

I'm writing a simple Rails model called Person that has_many :phone_numbers and I'm trying to save the phone numbers in a complex form without manually writing setter methods. accepts_nested_attributes_for should do what I want but I'm having trouble getting it to work. Here's the code I have so far:
Migration
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.string :first_name
t.string :last_name
t.integer :address_id
t.string :email
t.timestamps
end
end
def self.down
drop_table :people
end
end
class CreatePhoneNumbers < ActiveRecord::Migration
def self.up
create_table :phone_numbers do |t|
t.string :number, :limit => 10
t.string :extension, :limit => 5
t.string :description, :null => false
t.integer :telephone_id
t.string :telephone_type
t.timestamps
end
end
def self.down
drop_table :phone_numbers
end
end
Models
class Person < ActiveRecord::Base
has_one :address, :as => :addressable, :dependent => :destroy
has_many :phone_numbers,
:as => :telephone,
:dependent => :destroy
accepts_nested_attributes_for :phone_numbers
attr_protected :id
validates_presence_of :first_name, :last_name, :email
end
class PhoneNumber < ActiveRecord::Base
attr_protected :id
belongs_to :telephone, :polymorphic => true
end
View
<% form_for #person, :builder => CustomFormBuilder do |f| %>
<%= f.error_messages %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<% fields_for "person[address]", #person.address, :builder => CustomFormBuilder do |ff| %>
<%= ff.text_field :address_1 %>
<%= ff.text_field :address_2 %>
<%= ff.text_field :city %>
<%= ff.text_field :state %>
<%= ff.text_field :zip %>
<% end %>
<h2>Phone Numbers</h2>
<% #person.phone_numbers.each do |phone_number| %>
<% fields_for "person[phone_numbers][]", phone_number, :builder => CustomFormBuilder do |ff| %>
<%= ff.text_field :description %>
<%= ff.text_field :number %>
<%= ff.text_field :extension %>
<% end %>
<% end %>
<%= f.text_field :email %>
<%= f.submit 'Create' %>
<% end %>
Controller
def new
#person = Person.new
#person.build_address
#person.phone_numbers.build
respond_to { |format| format.html }
end
def create
#person = Person.new(params[:person])
respond_to do |format|
if #person.save
flash[:notice] = "#{#person.name} was successfully created."
format.html { redirect_to(#person) }
else
format.html { render :action => 'new' }
end
end
end
I have verified that a phone_numbers= method is being created, but the post still causes:
PhoneNumber(#69088460) expected, got HashWithIndifferentAccess(#32603050)
RAILS_ROOT: H:/projects/test_project
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_proxy.rb:263:in `raise_on_type_mismatch'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_collection.rb:319:in `replace'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_collection.rb:319:in `each'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_collection.rb:319:in `replace'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations.rb:1290:in `phone_numbers='
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2740:in `send'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2740:in `attributes='
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2736:in `each'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2736:in `attributes='
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2434:in `initialize'
H:/projects/salesguide/app/controllers/accounts_controller.rb:46:in `new'
H:/projects/test_project/app/controllers/accounts_controller.rb:46:in `create'
I can get this to work by manually writing the phone_numbers= method, but this would cause a tremendous duplication of effort, I would much rather learn how to do this right. Can anybody see what I'm doing wrong?
You're forgetting the to call fields_for as a method on the person form. Otherwise you're not actually using fields_for in a accept_nested_attributes_for context. Michael's solution tries to trick Rails into treating the submission as a properly defined accepts_nested_attributes_for form.
The correct syntax for what you are trying to do is:
parent_form_object.fields_for id, object_containing_values, {form_for options}, &block
You'll find the code looks cleaner and simpler to debug if you provide a symbol as id, containing the association name of the child model as defined in your Person model.
Also, the each block you're using might cause problems if #person.phone_numbers is empty. You can ensure that there is at least one set of Phone Number fields with a line similar to the one I used with
<% #phs = #person.phone_numbers.empty? ? #person.phone_numbers.build : #person.phone_numbers %>
With all corrections, this code will do what you want it to.
View
<% form_for #person, :builder => CustomFormBuilder do |f| %>
<%= f.error_messages %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<% f.fields_for :address, #person.address, :builder => CustomFormBuilder do |address_form| %>
<%= address_form.text_field :address_1 %>
<%= address_form.text_field :address_2 %>
<%= address_form.text_field :city %>
<%= address_form.text_field :state %>
<%= address_form.text_field :zip %>
<% end %>
<h2>Phone Numbers</h2>
<% #phs = #person.phone_numbers.empty? ? #person.phone_numbers.build : #person.phone_numbers %>
<% f.fields_for :phone_numbers, #phs, :builder => CustomFormBuilder do |phone_number_form| %>
<%= phone_number_form.text_field :description %>
<%= phone_number_form.text_field :number %>
<%= phone_number_form.text_field :extension %>
<% end %>
<%= f.text_field :email %>
<%= f.submit 'Create' %>
<% end %>
You might find it useful to check out the complex-form-examples repository on github for a working example. It also comes with code to dynamically add new entries for :has_many relationships from the view/form.
I was playing around with accepts_nested_attributes_for yesterday when trying to figure out Rails form with three models and namespace. I needed to setup the form slightly differently, try using: person[phone_numbers_attributes][]

Resources