Polymorphic Associaton transaction fails in Rails 5 - ruby-on-rails

I am unable to get my association to save in localhost:3000/controller_name/new. I believe it is due to belongs_to failing validation, but I am not sure how to fix it (beyond just dropping the association via requires:false/optional: true, the consequences of which I am not sure of). I created my association following this tutorial but it was for a previous version of rails.
I have a polymorphic address table that can belong to events, businesses, users, etc. I am trying to add it to event.
Address migration - you can see it references addressable:
class CreateAddresses < ActiveRecord::Migration[5.1]
def change
create_table :addresses do |t|
t.string :address
t.decimal :latitude, null: false, precision: 10, scale: 6, index: true
t.decimal :longitude, null: false, precision: 10, scale: 6, index: true
t.references :addressable, polymorphic: true, index: true
end
end
end
Address model:
class Address < ApplicationRecord
belongs_to :addressable, polymorphic: true
end
Event model:
class Event < ApplicationRecord
has_one :address, as: :addressable
accepts_nested_attributes_for :address
end
Event Controller:
class EventsController < ApplicationController
#...stuff...
# GET /events/new
def new
#event = Event.new
#event.address = #event.build_address
##event.address = Address.new(addressable: #event)
##event.address = #event.create_address
##event.address = #addressable.User.new
end
#...stuff...
You can see I tried multiple methods to create the event's address, they mostly create the below item, the ones using addressable cause a Nil crash.
#<Address id: nil, address: nil, latitude: nil, longitude: nil, addressable_type: "Event", addressable_id: nil>
Event Form (Uses Simple_form gem):
<%= simple_form_for #event do |f| %>
<%= f.input :name %>
<%= f.input :description %>
<%= f.simple_fields_for :address do |address| %>
<%= render :partial => 'shared/address/form', :locals => {:f => address} %>
<% end %>
<%= f.button :submit %>
<% end %>
Address form partial:
<!-- Google Maps Must be loaded -->
<% content_for :head do %>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCMh8-5D3mJSXspmJrhSTtt0ToGiA-JLBc&libraries=places"></script>
<% end %>
<div id="map"></div>
<%= f.input :address %>
<%= f.input :latitude %>
<%= f.input :longitude %>
Forms render fine. When I try to save I get
Started POST "/events" for 127.0.0.1 at 2017-07-01 16:06:23 -0400
Processing by EventsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"R0zlSs3UUNV3x8sQn5ocmE4jP12uOsFza7FezBAuhP4sw2MhF1OhixF8sAfDsLpfMEX7x5rhJ9HZfbKna8ncEA==", "event"=>{"name"=>"asd", "description"
=>"asd", "address_attributes"=>{"address"=>"asd", "latitude"=>"1", "longitude"=>"1"}}, "commit"=>"Create Event"}
(0.1ms) begin transaction
(0.1ms) rollback transaction
And I am kept on the new page. If I insert a byebug into create and print out #event.errors it shows:
#<ActiveModel::Errors:0x007fb29c34a9a8 #base=#<Event id: nil, name: "asd", description: "asd", min_users: nil, max_users: nil, start_time: nil, recurring: nil, created_at: nil, upd
ated_at: nil>, #messages={:"address.addressable"=>["must exist"]}, #details={:"address.addressable"=>[{:error=>:blank}]}>
How can I create the address.addressable? What are the consequences of turning off the requires validation as some SO answers suggest?

run rake db:schema:dump, and then check file inside db/schema.rb, make sure you have 2 fields as follow
* t.integer :addressable_id,
* t.string :addressable_type
and for more detail here is link about Activerecord Polymorphic Associations, if you have problem with your schema, then you can run migration as follow
t.references :addressable, polymorphic: true, index: true
as event has many address through polymorphic association, you can use this link to create the address
and below is sample code
#address = #event.addresses.build(attributes = {}, ...)
you may use #address not #event.address

Discovered the issues and wrote a blog post discussing this in depth. Essentially I was encountering 2 separate problems
1.The error I was receiving - "address.addressable"=>["must exist"]. address.addressable is the 'parent' table's tuple ID. In my case it is the Event ID. This error is trying to tell me that this foreign key is missing from the new address when we try to save it in the controller's create function. Like I said in the question, you can set optional:true to ignore this problem, and it somehow magically gets filled in after saving the Event. Or you can manually assign it in the create function before it saves.
def create
#event = Event.new(event_params)
#event.address.addressable = #event #<<<<<<<<<<< manually assign address.addressable
respond_to do |format|
if #event.save #Saves the event. Addressable has something to reference now, but validation does not know this in time
format.html { redirect_to #event, notice: 'Event was successfully created.' }
format.json { render :show, status: :created, location: #event }
else
#dont forget to remove the 'byebug' that was here
format.html { render :new }
format.json { render json: #event.errors, status: :unprocessable_entity }
end
end
end
2.Event ID was a string. In my address migration I was using t.references :addressable, polymorphic: true, index: true which is an alias for and addressable_id of integer type. I needed to change my migration to use string IDs - so delete the references line and add this in instead.
t.string :addressable_id, index: true
t.string :addressable_type, index: true
This is covered in slightly more detail in the blog post.

Related

comment.user.username not working on show.html.erb

i'mtrying a simple feature where a user can comment on inquest post , but comment .user.username is not working ,it's rendering comment.user but does not support user attributes
create_table "comments", force: :cascade do |t|
t.string "content"
t.integer "inquest_id"
t.integer "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["inquest_id"], name: "index_comments_on_inquest_id"
t.index ["user_id"], name: "index_comments_on_user_id"
end
comment_model
class Comment < ApplicationRecord
belongs_to :inquest
belongs_to :user
end
user_model is simple with has many comments association
comments create method of controller
def create
#comment = Comment.new(comment_params)
pp comment_params
#inquest = Inquest.find(params[:inquest_id])
#comment = Comment.new(comment_params)
#comment.inquest = #inquest
#comment.user = current_user
respond_to do |format|
if #comment.save
format.js do
#inquest = Inquest.find(params[:inquest_id])
end
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
I'm rendering comments in inquest's show.html.erb
Showing /Users/zunairaihsan/Desktop/fyp_ed_bolt/app/views/inquests/show.html.erb
where line #123 raised:
undefined method `user_name' for nil:NilClass
I've tried most of the ways possible , but it's not working.please let me know where I'm wrong
I assume, in inquests/show.html.erb you're displaying multiple comments, something like
<%= #inquest.comments.each do |comment| %>
<%= comment.user.user_name %>
<%= comment.content %>
<% end %>
Many comments will render without issue. Comment model and database doesn't allow user_id to be nil.
But looks like one comment's user_id doesn't have a corresponding id in users table. When you try to figure out what's going on and remove user_name
<%= #inquest.comments.each do |comment| %>
<%= comment.user %>
<%= comment.content %>
<% end %>
Sneaky broken comment probably doesn't show you anything, comment.user is nil, and because you have no validation on comment.content it could also be nil.
First, get rid of comments without user to verify this is the issue:
# this is fast enough for a few thousand comments
>> Comment.find_each { |comment| comment.destroy unless comment.user }
After this inquests/show.html.erb should be working.
To make sure this doesn't happen again:
class User
# this will delete all `user.comments` when you destroy `user`
has_many :comments, dependent: :destroy
# ...
end
To really make sure this doesn't happen again:
class CreateComment < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.references :user, null: false, foreign_key: true
# ...
end
end
end
With foreign_key constraint, your database will not let you destroy a user if they have comments. This works in tandem with dependent: :destroy. If you delete a user and rails automatically destroys all user.comments, then database will not complain.
Probably do the same for inquest as well if it's not optional.
Also comments without content are not really comments:
class Comment < ApplicationRecord
belongs_to :inquest
belongs_to :user
validates :content, presence: true
end

Validation failed Class must exist

I have been (hours) trouble with associations in Rails. I found a lot of similar problems, but I couldn't apply for my case:
City's class:
class City < ApplicationRecord
has_many :users
end
User's class:
class User < ApplicationRecord
belongs_to :city
validates :name, presence: true, length: { maximum: 80 }
validates :city_id, presence: true
end
Users Controller:
def create
Rails.logger.debug user_params.inspect
#user = User.new(user_params)
if #user.save!
flash[:success] = "Works!"
redirect_to '/index'
else
render 'new'
end
end
def user_params
params.require(:user).permit(:name, :citys_id)
end
Users View:
<%= form_for(:user, url: '/user/new') do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :citys_id, "City" %>
<select name="city">
<% #city.all.each do |t| %>
<option value="<%= t.id %>"><%= t.city %></option>
<% end %>
</select>
end
Migrate:
class CreateUser < ActiveRecord::Migration[5.0]
def change
create_table :user do |t|
t.string :name, limit: 80, null: false
t.belongs_to :citys, null: false
t.timestamps
end
end
Message from console and browser:
ActiveRecord::RecordInvalid (Validation failed: City must exist):
Well, the problem is, the attributes from User's model that aren't FK they are accept by User.save method, and the FK attributes like citys_id are not. Then it gives me error message in browser saying that "Validation failed City must exist".
Thanks
Try the following:
belongs_to :city, optional: true
According to the new docs:
4.1.2.11 :optional
If you set the :optional option to true, then the presence of the
associated object won't be validated. By default, this option is set
to false.
This comes a bit late but this is how to turn off this by default in rails 5:
config/initializers/new_framework_defaults.rb
Rails.application.config.active_record.belongs_to_required_by_default = false
In case you don't want to add optional: true to all your belongs_to.
I hope this helps!
You need to add the following to the end of the belongs_to relationship statement:
optional: true
It is possible to set this on a global level so that it works in the same way as older versions of rails, but I would recommend taking the time to manually add it to the relationships that really need it as this will cause less pain in the future.
I found out a solution to the problem "Validation failed: Class must exist" and it's better than use:
belongs_to :city, optional: true
4.1.2.11 :optional
If you set the :optional option to true, then the presence of the associated object won't be validated. By default, this option is set to false.
cause you still make a validation in application level. I solve the problem making my own validation in create method and changing user_params method:
def create
#city = City.find(params[:city_id])
Rails.logger.debug user_params.inspect
#user = User.new(user_params)
#user.city_id = #city.id
if #user.save!
flash[:success] = "Works!"
redirect_to '/index'
else
render 'new'
end
end
def user_params
params.require(:user).permit(:name)
end
I didn't test this code, but it works in another project mine. I hope it can help others!
Rails 5
If you have a belongs_to relationship to :parent then you have to pass an existing parent object or create a new one then assign to children object.
belongs_to :city, required: false
params.require(:user).permit(:name, :citys_id)
It's a mistake, isn't it? (citys_id vs city_id)
Rails.application.config.active_record.belongs_to_required_by_default = false
This works because the Rails 5 has true by default
to disable you go under Initilizers then click on the New_frame-work and turn the true to false

Building an messaging system from scratch with multiple recipients in rails 4

Ive been trying to build an messaging system for my site which uses devise for authentication. The functionality it requires is to be able to send a message to either one or more recipients (preferably with a checklist form listing users as well). After searching for a while I found a couple gems such as mailboxer, but I didn't need all its features and wanted to build my own system for sake of learning (still a newbie at rails).
I have followed this ancient tutorial ( http://web.archive.org/web/20100823114059/http://www.novawave.net/public/rails_messaging_tutorial.html ). I realize this is a very old tutorial but it is the only one I could find which matched what I was trying to achieve.
I have followed the tutorial to a T and even copied and pasted the code from the tutorial after my code didn't work.
when trying to access http://localhost:3000/mailbox i get a NoMethodError in MailboxController#index
undefined method `messages' for nil:NilClass
app/controllers/mailbox_controller.rb:12:in `show'
app/controllers/mailbox_controller.rb:6:in `index'
I have also referenced this question Rails 3: undefined method messages for Folder which had the same error as me but the topic just seemed to go no where.
mailbox_controller.rb
class MailboxController < ApplicationController
def index
#folder = current_user.inbox
show
render :action => "show"
end
def show
#folder ||= current_user.folders.find_by(params[:id])
#messages = #folder.messages :include => :message, :order => "messages.created_at DESC"
end
end
models/folder.rb
class Folder < ActiveRecord::Base
acts_as_tree
belongs_to :user
has_many :messages, :class_name => "MessageCopy"
end
Any help with this would be awesome, also just let me know if you need any more info and will post it.
I ended up figuring out the messaging system with a few modifications. I wanted to post my whole solution since it gave me a difficult time and might be useful to others. I kept it very simple and did not include the the folder model which was giving me the problem in the first place, but none the less it is functioning.
Heres are my associations
model/message.rb
attr_reader :user_tokens
belongs_to :sender, :class_name => 'User'
has_many :recipients
has_many :users, :through => :recipients
def user_tokens=(ids)
self.user_ids = ids
end
model/recipient.rb
belongs_to :message
belongs_to :user
model/user.rb
has_many :messages, :foreign_key => :sender_id
This is my messages controller
messages_controller.rb
def new
#message = Message.new
#user = current_user.following
#users = User.all
# #friends = User.pluck(:name, :id).sort
end
def create
#message = current_user.messages.build(message_params)
if #message.save
flash[:success] = "Message Sent!"
redirect_to messages_path
else
flash[:notice] = "Oops!"
render 'new'
end
end
def index
#user = User.find(current_user)
#messages = Recipient.where(:user_id => #user).order("created_at DESC")
end
private
def message_params
params.require(:message).permit(:body, :sender_id, user_tokens: [])
end
My Views
_form.html.erb
</div>
<!-- displays the current users frinds their following -->
<%= f.select :user_tokens, #user.collect {|x| [x.name, x.id]}, {}, :multiple => true, class: "form-control" %>
<br>
<div class="modal-footer">
<%= f.button :submit, class: "btn btn-primary" %>
</div>
Schema
messages_table
t.text "body"
t.integer "sender_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "body_html"
recipients_table
t.integer "message_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
I hope this helps.

How to create my specific error message for model property in Rails?

I have my Model :
class Course < ActiveRecord::Base
include ActiveUUID::UUID
validates :location, :description, presence: true
end
and DB model :
class CreateCourses < ActiveRecord::Migration
def change
create_table :courses, :id => false do |t|
t.uuid :id, :primary_key => true, :null => false
t.datetime :date_start, :null => false
t.float :price, :null => false
t.datetime :date_end
t.text :description
t.text :location, :null => false
t.uuid :mentor_uuid
t.timestamps
end
end
end
and my method in controller which i cal via ajax :
def create
#course = Course.new(params.require(:course).permit(:description, :location))
if #course.save
render json: #course, status: 200
else
render json: #course.errors.full_messages, status: 400
end
end
and here is my ajax call :
var course = {"description" : "Timo", 'location' : 'hahahah'};
$scope.test = function(){
$http.post('api/courses.json', course).success( function (data) {
}).success(function(data){
console.log(data);
}).error(function(data){
console.log(data);
});
};
So this return me an error with : ["Date end can't be blank", "Date start can't be blank", "Price can't be blank"], but what if i want to change error message to my specific one? Also, on error it only returns errors, hwo do i make rails also return "old" model which faild so user dont have to hit face agains the wall and type all the text again from scratch?
Attribute Names
As per this answer & this one, the core problem you have is your attribute name is prepending the error message. The way to fix this is to some how remove the attribute name from the error message:
#config/locales/en.yml
en:
errors:
format: "%{message}"
The default is %{attribute} %{message}
--
We've done it this way before, although I don't think this works in Rails 4:
<%= #model.errors.full_messages.each do |attribute, message| %>
<%= message %>
<% end %>

Rails 3 - Model association (composition) and method delegation

I have the following models and associations:
class JuridicalPerson < ActiveRecord::Base
end
class Supplier < ActiveRecord::Base
belongs_to :juridical_person
delegate :company_name, :company_name=, :to => jurirical_person
end
The controler is:
def new
#supplier = Supplier.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #supplier }
end
end
The schema looks as follow:
create_table "suppliers", :force => true do |t|
t.integer "juridical_person_id"
...
end
create_table "juridical_people", :force => true do |t|
t.string "company_name"
...
end
Now when I try to render it in a view, I get the following error:
Supplier#company_name delegated to juridical_person.company_name, but juridical_person is nil: #(Supplier id: nil, juridical_person_id: nil, created_at: nil, updated_at: nil)
Extracted source (around line #9):
8: <%= f.label :company_name, "Company Name" %>
9: <%= f.text_field :company_name %>
Seems like the associated juridical_person is not being created at the time of delegation, but I can't figure out why. Even if I create it in the controller, the app will break when trying to update for the same reason. What am I missing?
remove = Change
delegate :company_name, :company_name=, :to => jurirical_person
To
delegate :company_name, :company_name, :to => jurirical_person
class JuridicalPerson < ActiveRecord::Base
has_many :suppliers
end

Resources