One to Many Association Object Will Not Save - ruby-on-rails

I set up a has_many belongs_to relationship in my two models and followed Ryan Bates' screencast on how to set up the controller. When I submit my form to create the new object, the nested object does not save for some reason. Here are my models:
class Auction < ActiveRecord::Base
has_many :bids, dependent: :destroy
end
class Bid < ActiveRecord::Base
belongs_to :auction
belongs_to :user
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :auction_id, presence: true
end
and my nested object controller:
class BidsController < ApplicationController
def index
#auction = Auction.find(params[:auction_id])
#bids = #auction.bids
end
def new
#auction = Auction.find(params[:auction_id])
#bid = #auction.bids.build
end
def create
#auction = Auction.find(params[:auction_id])
#bid = #auction.bids.create(params[:bid])
#bid.save
if #bid.save
flash[:success] = "Bid has been successfully placed."
else
#bid.errors
render 'new'
end
end
def destroy
#auction = Auction.find(params[:auction_id])
#bid = #auction.bids.find
#bid.destroy
flash[:notice] = "Successfully destroyed Bid."
redirect_to auction_url(#bid.article_id)
end
end
my form:
<h1>Create a New Bid</h1>
<%= form_for ([#auction, #bid]) do |f|%>
<p>
<%= f.submit %>
</p>
<%end%>
and my terminal output:
Started POST "/auctions/1/bids" for 127.0.0.1 at 2014-11-30 17:59:13 -0600
Processing by BidsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"dkZBcab1rgZjtJGF3LAJ//exK6liglZ0Fy4mg7HWEt0=", "commit"=>"Create Bid", "auction_id"=>"1"}
Auction Load (0.1ms) SELECT "auctions".* FROM "auctions" WHERE "auctions"."id" = ? LIMIT 1 [["id", 1]]
(0.0ms) begin transaction
(0.0ms) commit transaction
(0.0ms) begin transaction
(0.0ms) rollback transaction
(0.0ms) begin transaction
(0.0ms) rollback transaction
Thanks for your help.

Your bid object needs a user_id because you have validates :user_id, presence: true in the class definition.
When you call #bid.save in the controller, however, #bid does not have a user_id value, therefore the transaction gets rolled back because of the failing validation.
You should be able to see this by looking at #bid.errors.full_messages in the create action, after you've called #bid.save. (Look up the pry gem if you're not already familiar with it...it would be a perfect tool to let you do this inspection.)
Try replacing your create action with this:
def create
#auction = Auction.find(params[:auction_id])
#bid = #auction.bids.new params[:bid].merge(user_id: current_user.id)
if #bid.save
flash[:success] = "Bid has been successfully placed."
else
flash[:error] = #bid.errors.full_messages.join('. ')
render 'new'
end
end
This assumes that you have access to the current user in the controller as current_user. Devise and other popular auth solutions supply this, or you can do so yourself.
Note also that your original code tries to write #bid to the database 3 separate times, which is twice more than you need to. Here are the offending lines:
def create
...
#bid = #auction.bids.create(params[:bid])
#bid.save
if #bid.save
...
#create instantiates an object and attempts to write it to the database. In my code above, I've replaced #auction.bids.create(params...) with #auction.bids.new(params...). This initializes #bid without trying to persist it to the db.
I also removed the first #bid.save because the line below it if #bid.save will accomplish the same thing.
Finally, your line #bid.errors doesn't do anything useful. I modified it to store the error messages in your flash hash, which you can then use in your view to display the errors to the user.

Related

Ruby on Rails - ActiveAdmin Posts

i followed a Youtube Video to implement ActiveAdmin Theme in my Rails App,- and everything worked like a charm (i thought).
https://www.youtube.com/watch?v=i2x995hm8r8
I followed every Step he took and i am a little confused right now because i can't create posts.
Whenever i try to create a New Post and type in the tile, body and select a image- it just won't do anything. It doesn't even give me a Error Message.
posts_controller.rb
class PostController < ApplicationController
def index
#post = Post.all.order('created_at DESC')
end
def create
#post = Post.new(params[:post].permit(:title, :body))
if #post.save
redirect_to #post
else
render 'new'
end
end
def show
#post = Post.find(params[:id])
#post = Post.order("created_at DESC").limit(4).offset(1)
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
if #post.update(params[:post].permit(:title, :body))
redirect_to #post
else
render 'edit'
end
end
def destroy
#post = Post.find(params[:id])
#post.destroy
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title, :body)
end
end
ActiveAdmin.register Post do
posts.rb
permit_params :title, :body, :image
show do |t|
attributes_table do
row :title
row :body
row :image do
post.image? ? image_tag(post.image.url, height: '100') : content_tag(:span, "No image yet")
end
end
end
form :html => {:multipart => true} do |f|
f.inputs do
f.input :title
f.input :body
f.input :image, hint: f.post.image? ? image_tag(post.image.url, height: '100') : content_tag(:span, "Upload JPG/PNG/GIF image")
end
f.actions
end
end
post.rb
class Post < ApplicationRecord
belongs_to :user
validates :title, presence: true, length: {minimum: 5}
validates :body, presence: true, length: { maximum: 140}
has_attached_file :image, styles: { medium: "300x300>", thumb: "100x100>" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
end
EDIT I:
Started POST "/admin/posts" for 127.0.0.1 at 2018-03-20 14:30:24 +0100
Processing by Admin::PostsController#create as HTML
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"f+hfBD3lzgXEfz1q38/i3YciHsbb5LYWbbHUUsyIeOCaNSUReUUVVTBE//Dw0zXxSuFCzcMfYuUGDtIJlNb58w==", "post"=>{"title"=>"asdasdasd", "body"=>"asdasdasd"}, "commit"=>"Create Post"}
AdminUser Load (0.3ms) SELECT "admin_users".* FROM "admin_users" WHERE "admin_users"."id" = $1 ORDER BY "admin_users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.2ms) BEGIN
(0.1ms) ROLLBACK
Rendering /Users/useruser/.rvm/gems/ruby-2.4.2/bundler/gems/activeadmin-2cf85fb03ab3/app/views/active_admin/resource/new.html.arb
Rendered /Users/useruser/.rvm/gems/ruby-2.4.2/bundler/gems/activeadmin-2cf85fb03ab3/app/views/active_admin/resource/new.html.arb (134.6ms)
Completed 200 OK in 230ms (Views: 148.3ms | ActiveRecord: 5.7ms)
If you need more of my Code just tell me what i should post in here.
I am just getting started with Ruby on Rails and Programming overall, so yes indeed, i am a Newb.
Thanks in advance!
From what I see in your edit 1, I see that you render new after submiting form. It means that your Post is not being saved. It also means that your application does exactly what it should do.
I assume that you are using latests Rails 5.
In Post model, you have belongs_to association (Post belongs to User).
In Rails 5, that means user or user_id has to be provided, to create Post (Post cannot belong to noone), else you won't be able to save.
Depending how you created association in table, you might be able to pass user or user_id in params.
Another way to create post belonging to particular user is:
#user = User.first
#post = #user.posts.build(post_params)
For ActiveAdmin, you can use default form that is created based on your model.
Just make sure you permit all params when creating it that way
ActiveAdmin.register Post do
permit_params %i[title body image user_id]
...
end
You can also set belongs_to :user association as optional.
Now some general advices from me:
First of all use proper indentation.
My advice to you is install Rubocop gem.
Second:
def show
#post = Post.find(params[:id])
#post = Post.order("created_at DESC").limit(4).offset(1)
end
That doesn't make much sense, you overwrite instance variable just after first assignment.
#post = Post.order("created_at DESC").limit(4).offset(1) is more of an index action, since it does not show particular posts, it show 2..5 newest posts.
def post_params
params.require(:post).permit(:title, :body)
end
Misses image attribute.
def update
#post = Post.find(params[:id])
if #post.update(params[:post].permit(:title, :body))
redirect_to #post
else
render 'edit'
end
end
You duplicate params[:post].permit(:title, :body). You hgave already created private method for that. Use it here. Same goes for creation action, you duplicated it there too. Read what DRY code is about (google it).
You have belongs_to :user but never set a user_id. A belongs_to relation is by default required (in recent rails 4/5), and this will block the saving. The easiest way, for now, to fix this is to write it as following
belongs_to :user, optional: true
[EDIT: how to store current-user as owner of post]
If you want to automatically set the user to the currently logged in user, which I think is the intention, you can add the following to your active-admin configuration:
ActiveAdmin.register Post do
# .. keep your original configuration here ...
before_build do |record|
record.user = current_user
end
end
Or add an extra field to the form allowing the administrator to select the user?

Add to Cart works but nothing in Show#Cart

Here's my cart.rb model
class Cart < ActiveRecord::Base
has_many :tutors
def current_cart
if session[:cart_id]
#current_cart ||= Cart.find(session[:cart_id])
end
if session[:cart_id].nil?
#current_cart = Cart.create!
session[:cart_id] = #current_cart.id
end
#current_cart
end
def add_tutor(tutor_id)
tutor = Tutor.find(tutor_id)
if tutor
self.tutors << tutor
end
save
end
end
And here's my carts_controller.rb
class CartsController < ApplicationController
def show
#cart = current_cart
end
def checkout
#cart = current_cart
name = params[:checkout][:your_name]
email = params[:checkout][:your_email]
message = params[:checkout][:your_hp]
ApplicationMailer.checkout(#cart, name, email, message).deliver
flash[:success] = "We have received your request and will be in touch with you shortly!"
redirect_to root_path
end
def add_to_cart
#cart = current_cart.add_tutor(params[:tutor_id])
if #cart
flash[:success] = "You have successfully added the tutor to your cart!"
redirect_to tutors_path
else
flash[:danger] = "Oops! Something went wrong. Please try again!"
redirect_to root_path
end
end
end
And here's the button for the add to cart function
<%= button_to "Shortlist Tutor", add_to_cart_path(:tutor_id => tutor.id), :method => :post %>
The add to cart function seems to work fine as it redirects me to the correct page and no errors or anything like that seems to show up. In my rails console i can see in the server that there isn't any rollback.
Processing by CartsController#add_to_cart as HTML
Parameters: {"authenticity_token"=>"da3XDg69FSCrEyn39v8Apty4aX40TJH85BeW49x/4R3MElKYxip1w7rpbWRBYj5hhZDAivf7Bxn4FK1dkHyKpg==", "tutor_id"=>"3"}
Cart Load (0.2ms) SELECT "carts".* FROM "carts" WHERE "carts"."id" = ? LIMIT 1 [["id", 12]]
Tutor Load (0.3ms) SELECT "tutors".* FROM "tutors" WHERE "tutors"."id" = ? LIMIT 1 [["id", 3]]
(0.1ms) begin transaction
(0.1ms) commit transaction
(0.1ms) begin transaction
(0.1ms) commit transaction
Thats what i see in the server. I'm really not quite sure why don't i see anything when i go to my carts#show view.
Would appreciate any help that i can get. Thanks!
Check what you are receiving in params[:tutor_id].
Looking at SQL statements that are executed it doesn't look like it is saving at all when invoking add_to_cart. It only carries out a SELECT on carts and tutor.

Rails Edit/Update action not working with nested items

I've got working new/create actions and a form for my #miniature model and it's nested model #scales. I can't get the update/edit actions right. Should be simple but I'm very stuck.
#miniature has_many #scales through #sizes.
In my #miniature model I have
has_many :sizes, dependent: :destroy
has_many :scales, :through => :sizes
accepts_nested_attributes_for :sizes, allow_destroy: true
In the controller I have
def new
#miniature = Miniature.new
#all_scales = Scale.all
#size = #miniature.sizes.build
end
def create
#miniature = Miniature.new(miniature_params)
params[:scales][:id].each do |scale|
if !scale.empty?
#miniature.sizes.build(:scale_id => scale)
end
end
if #miniature.save
redirect_to miniature
else
render 'new'
end
end
private
def miniature_params
params.require(:miniature).permit(:name, :release_date, :material, :pcode, :notes, sizes_attributes: [:id, :scale_id, :miniset_id])
end
That works but edit and update actions do not. I have my edit set up the same as my new.
def edit
#miniature = Miniature.find(params[:id])
#all_scales = Scale.all
#size = #miniature.sizes.build
end
I had thought that updating miniature params would update the #sizes model but it doesn't
def update
#miniature = Miniature.find(params[:id])
if #miniature.update_attributes(miniature_params)
flash[:success] = "Miniature updated"
redirect_to #miniature
else
render 'edit'
end
end
It currently updates the #miniature but not the #sizes info. Is my problem in the edit or in the update or in both?
The relevant bit of the form:
<%= f.fields_for(#size) do |sf| %>
<%= sf.label simple_pluralize(#miniature.scales.count, 'Scale') %>
<%= collection_select( :scales, :id, #all_scales, :id, :name,
{:selected => #miniature.scales.map(&:id)},
{class: 'multiselect', multiple: true}) %>
<% end %>
Any help or pointers to further reading very much appreciated. Even if it's just to say "You're overlooking this obvious thing, go and do some more reading/work".
It seems likely I need to have an update action more similar to my create action's if statement?
UPDATE for JKen13579
This is the PATCH request from my server log when submitting an edit:
Started PATCH "/miniatures/21" for 127.0.0.1 at 2014-04-02 16:00:10 +0100
Processing by MiniaturesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jQ79L1Exx83C47jnCF3nsWQ2tV07tRwKfI8wNeLzojo=", "miniature"=>{"name"=>"Test Miniature", "material"=>"Metal", "pcode"=>"123123123123123", "release_date(1i)"=>"2013", "release_date(2i)"=>"2", "release_date(3i)"=>"2", "notes"=>""}, "scales"=>{"id"=>["", "2"]}, "commit"=>"Save changes", "id"=>"21"}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = 4 ORDER BY "users"."id" ASC LIMIT 1
Miniature Load (0.2ms) SELECT "miniatures".* FROM "miniatures" WHERE "miniatures"."id" = ? LIMIT 1 [["id", "21"]]
(0.1ms) begin transaction
(0.2ms) commit transaction
Redirected to http://localhost:3000/miniatures/21
Completed 302 Found in 12ms (ActiveRecord: 1.3ms)
UPDATE for Observer
I'm using fields_for because that's what I managed to get the new/create action to work with. I'm not tied to it if there is a better way, at least as far as I know.
My routes has
resources :miniatures do
collection do
get :scales
get :collections
get :lines
get :contents
post :import
end
member do
get :minisets, :setminis
end
end
and further down
resources :sizes
resources :scales
I think my routes file is generally a bit cluttered and is due for a refactoring.
As per the server log entry(see below), I see that in params hash, scales (highlighted in bold) is being passed as a separate hash and not within miniature:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jQ79L1Exx83C47jnCF3nsWQ2tV07tRwKfI8wNeLzojo=",
"miniature"=>{"name"=>"Test Miniature", "material"=>"Metal",
"pcode"=>"123123123123123", "release_date(1i)"=>"2013",
"release_date(2i)"=>"2", "release_date(3i)"=>"2", "notes"=>""},
"scales"=>{"id"=>["", "2"]}, "commit"=>"Save changes", "id"=>"21"}
Change the update action as:
def update
#miniature = Miniature.find(params[:id])
if params[:scales][:id]
## Convert ["", "1","2","4","8"] to [1,2,4,8]
params[:scales][:id] = params[:scales][:id].reject(&:empty?).map(&:to_i)
## Get the scale_id from sizes already present in database [1,2,5,6]
old_scales = #miniature.sizes.pluck(:scale_id)
## Find the new scales to be added [1,2,4,8] - [1,2,5,6] = [4,8]
new_scales = params[:scales][:id] - old_scales
## Find the old_scales to be deleted [1,2,5,6] - [1,2,4,8] = [5,6]
old_scales = old_scales - params[:scales][:id]
## Build new_scales [4,8]
new_scales.each do |scale|
#miniature.sizes.build(:scale_id => scale)
end
## Delete old_scales [5,6]
Size.delete_all(:scale_id => old_scales)
end
if #miniature.update_attributes(miniature_params)
flash[:success] = "Miniature updated"
redirect_to #miniature
else
render 'edit'
end
end
Update the create action so scale_id is passed as Integer and not String:
def create
#miniature = Miniature.new(miniature_params)
if params[:scales][:id]
## Convert ["", "1","2","4","8"] to [1,2,4,8]
params[:scales][:id] = params[:scales][:id].reject(&:empty?).map(&:to_i)
params[:scales][:id].each do |scale|
#miniature.sizes.build(:scale_id => scale)
end
end
if #miniature.save
redirect_to miniature
else
render 'new'
end
end

Sortable Table Row with Nested Resource

In my rails app, a Timesheet has_many Entries and an Entry belongs_to a Timesheet.
class Timesheet < ActiveRecord::Base
has_many :entries, order: 'position', dependent: :destroy
end
class Entry < ActiveRecord::Base
belongs_to :timesheet
end
I'm following Railscast 147 for sortable lists (the updated version). In the development log I notice that my params hash correctly updates the sort order, but on reload it doesn't save the positions correctly. Furthermore, the request is being processed by the create action instead of my custom sort action. Here's my controller.
class EntriesController < ApplicationController
before_filter :signed_in_user
before_filter :find_timesheet
def index
#entries = #timesheet.entries.order("position")
#entry = #timesheet.entries.build
end
def create
#entry = #timesheet.entries.build(params[:entry])
#entry.position = #timesheet.entries.count + 1
if #entry.save
#flash[:notice] = "Entry created"
#redirect_to timesheet_entries_path
respond_to do |format|
format.html { redirect_to timesheet_entries_path }
format.js
end
else
flash[:alert] = "Entry could not be added"
render 'new'
end
end
def destroy
#entry = #timesheet.entries.find(params[:id])
#entry.destroy
respond_to do |format|
format.html { redirect_to timesheet_entries_path, flash[:notice] = "Entry destroyed" }
format.js
end
end
def sort
params[:entry].each_with_index do |id, index|
#timesheet.entries.update_all({position: index+1}, {id: id})
end
render nothing: true
end
private
def find_timesheet
#timesheet = Timesheet.find(params[:timesheet_id])
end
end
and my routes.rb file.
Sledsheet::Application.routes.draw do
resources :timesheets do
resources :entries, only: [:index, :create, :destroy] do
collection { post :sort }
end
end
end
The entries.js.coffee
jQuery ->
$("#entries tbody").sortable(
helper: fixHelper
update: ->
$.post($(this).data('update-url'), $(this).sortable('serialize'))
).disableSelection()
The output from the development log
Started POST "/timesheets/8/entries" for 127.0.0.1 at 2012-06-04 20:14:18 -0400
Processing by EntriesController#create as */*
Parameters: {"entry"=>["60", "59", "61"], "timesheet_id"=>"8"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = 'qDs53hgOWfRMbNN9JKau3w' LIMIT 1
Timesheet Load (0.1ms) SELECT "timesheets".* FROM "timesheets" WHERE "timesheets"."id" = ? ORDER BY date DESC LIMIT 1 [["id", "8"]]
Completed 500 Internal Server Error in 2ms
NoMethodError (undefined method `stringify_keys' for "60":String):
app/controllers/entries_controller.rb:11:in `create'
I googled the error about the undefined method, but I'm confused why the create action would be called in this case anyway? I do have a new_entry form on the page, that creates a new entry via Ajax. Perhaps this is interfering with the sort? Any help would be appreciated!
The reason why there's no 'stringify_keys' method is because you're passing an array to the create action and not the sort action.
What do you have for data-update-url in your erb.html file?
Should be sort_entries_path.

How to delete multiple records simultaneously using Rails 3 and multiple check_box_tag elements

I'm stuck worse than a Yugo in a ditch.
I added the changes which ultimately made this work below (marked as bold edits).
Background
Widgets have many Gadgets
The final td column in the table has check_box_tags to select individual Gadget records for deletion
Desired Behavior
User should be able to click "checkall" as well as individual checkboxes to select Gadgets one at a time
Clicking the "Delete Selected" button should delete checked records without refreshing the page
EDIT: Adding more specific details about what's not working
Observed Behavior
destroy_multiple method in gadgets_controller.rb is never being called
instead, the create method appears to be called
Started POST "/widgets/1/gadgets" ...
Processing by GadgetsController#create as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"...=", "widget"=>{"gadget_ids"=>["all", "140", "139"]}, "widget_id"=>"1"}
Widget Load (0.3ms) SELECT "widgets".* FROM "widget" WHERE "widget"."id" = $1 LIMIT 1 [["id", "1"]]
Rendered gadgets/create.html.haml within layouts/application (1.1ms)
Rendered layouts/_header.html.haml (0.2ms)
Completed 200 OK in 36ms (Views: 19.0ms | ActiveRecord: 5.0ms)
I'm also unclear about this: {"gadget_ids"=>["all", "140", "139"]}.
"all" is the css id of 1 checkbox used to "checkall". In the checkbox.js.coffee file below, I attempt to remove the "all" value from the Array using javascript. When I log the Array to console, "all" is removed successfully. Yet it continues to be sent to the gadgets controller.
EDIT rails remote form populates the gadgets array for the POST request. just had to set it up correctly.
Models
widget.rb
class Widget < ActiveRecord::Base
has_many :gadgets
validates_presence_of :name
end
gadget.rb
class Gadget < ActiveRecord::Base
belongs_to :widget
validates_presence_of :widget_id
end
Gadgets Controller
class GadgetsController < ApplicationController
include ApplicationHelper
before_filter :get_widget
before_filter :widget_gadgets, only: [ :index ]
respond_to :json
def json_widgets
[ #widget, #gadgets ]
end
def index
respond_with(json_widgets) do |format|
format.json
format.html
end
end
def new
#gadget = #widget.gadgets.new()
end
def create
#gadget=#widget.gadgets.new(params[:gadget])
if #gadget.save
flash[:success] = "Gadget was successfully created"
redirect_to widget_gadgets_path
end
end
def destroy_multiple
gadget_ids=(params[:gadget_ids])
to_delete=[]
gadget_ids.split(',').each do |gadget_id|
to_delete.push(Gadget.find(gadget_id))
end
to_delete.each do |del|
del.destroy()
end
flash.now[:success] = "Gadget Destroyed Successfully"
respond_to do |format|
format.html { redirect_to widget_gadgets_path(#widget) }
format.json { render :json => to_delete.to_json }
end
end
def destroy
#gadget = gadget.find(params[:gadget_id])
#widget.gadgets.destroy(#gadget.id)
respond_to do |format|
format.html { redirect_to(widget_gadgets_path) }
end
end
def get_widget
begin
#widget = Widget.find(params[:widget_id])
rescue ActiveRecord::RecordNotFound
render file: "public/404.html", status: 404
end
end
private
def widget_gadgets
#widget=Widget.find(params[:widget_id])
#gadgets=#widget.gadgets unless #widget.gadgets.nil?
end
end
routes.rb
I'm trying to use a collection do block. Is that the right way to implement a destroy_multiple route?
DestroyMultiple::Application.routes.draw do
resources :widgets
resources :gadgets, :only => [ :new, :create ]
resources :widgets do
resources :gadgets do
collection do
post :destroy_multiple
end
end
end
match "/:id" => 'widget#gadgets'
root to: 'widgets#index'
end
After getting this to work properly, I wanted to post the full solution.
Rather than type it all out here, please see the full application on GitHub.
While this solution works, I would be very interested to know how I can optimize the solution. Thanks again to those who helped!
https://github.com/hernamesbarbara/AJAX-Rails-Full-CRUD

Resources