How to upload photos in rails 4? - ruby-on-rails

https://gist.github.com/Gtar69/27139cee82c75992ed82
Mac OS 10.9.3
Rails 4.1.0
Now I want to add photo upload to my website ! In that way, in terminal=>
$ rails g model Photo image_name:string product:references
$ rake db:migrate
$ rails g migration add_image_to_photos image:string
$ rake db:migrate
$ rails g controller Photos
Scheme as below:
ActiveRecord::Schema.define(version: 20140612034245) do
create_table "photos", force: true do |t|
t.string "image_name"
t.integer "product_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "image"
end
config/routes.rb
Rails.application.routes.draw do
devise_for :users
namespace :admin do
resources :products do
resources :photos
end
end
end
For new created controller, PhotosController shown below"
class PhotosController < ApplicationController
def create
#product = Product.find(params[:product_id])
#photo = #product.photos.create(photo_params)
redirect_to admin_product_path(#product)
end
private
def photo_params
params.require(:photo).permit(:image_name, :iamge)
end
end
Finally, adding "upload photos feature" in new.html.erb
<div class="panel panel-default">
<div class="panel-body">
<%= simple_form_for [:admin, #product] do|f| %>
<div class="group">
<%= f.input :title, label:'標題' %>
</div>
<div class="group">
<%= f.input :description, label:'敘述'%>
</div>
<div class = "group">
<%= f.input :quantity, label:'數量' %>
</div>
<h2>Add Photos</h2>
<%= form_for([#product, #product.photos.build]) do |f| %>
<% end %>
<%= f.submit "Submit", :disable_with => 'Submiting...' %>
</div>
</div>
However, I encounter the problem I can't understand =>
Rendered admin/products/new.html.erb within layouts/application (18.8ms)
Completed 500 Internal Server Error in 57ms
Showing /Users/Gtar/projects/artstore/app/views/admin/products/new.html.erb where line #26 raised:
undefined method `product_photos_path' for #<#<Class:0x000001013b2ef8>:0x00000101118670>
ActionView::Template::Error (undefined method `product_photos_path' for #<#<Class:0x000001013b2ef8>:0x00000101118670>):
23: </div> form_for([#article, #article.comments.build]) do |f| %>
24: -->
25: <h2>Add Photos</h2>
26: <%= form_for([#product, #product.photos.build]) do |f| %>
27: <% end %>
28: <%= f.submit "Submit", :disable_with => 'Submiting...' %>
29: </div>
app/views/admin/products/new.html.erb:26:in `block in _app_views_admin_products_new_html_erb__2984465001892677316_2165881840'
app/views/admin/products/new.html.erb:8:in `_app_views_admin_products_new_html_erb__2984465001892677316_2165881840'
Would you mind helping me for solve the issure?

Routes
For reference for future readers, here's your error:
undefined method `product_photos_path'
--
The problem here, as stated in the comments, is you're referencing a nested route, which doesn't exist. You'll need to do something like this in your routes:
#config/routes.rb
#no namespace
resources :products do
resources :photos #-> domain.com/products/1/photos
end
This will give you the route you need. You're currently using a namespace, which means you'll have to reference something like admin_product_photos_path
Paperclip
Something else you should consider is paperclip
This is a gem which helps you upload & save files to your database. It works much the same as you have now, except it uses its own datatable structure, with a call in your model to define the object:
#app/models/photo.rb
Class Photo < ActiveRecord::Base
has_attached_file :image
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
end
This will allow you to call the image object like this:
#product = Product.find params[:id]
#product.photos.first.image #-> image object from Paperclip
If you'd like more information, I would recommend you use this Railscast:

Related

Create objects based on received data from params in Ruby on Rails

Description
I am trying to create messages based on selected (via check box) users from the browser in Ruby on Rails.
Snapshot:
Steps to reproduce
My schema
ActiveRecord::Schema.define(version: 2021_11_13_142255) do
create_table "messages", force: :cascade do |t|
t.text "content"
t.integer "user_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "role"
t.integer "phone"
t.boolean "admin"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
end
messages_controller.rb
class MessagesController < ApplicationController
def new
#users = User.all
#message = Message.new(message_params)
end
def create
params[:user_objs].each do |u|
# "params.inspect" returns
# {"authenticity_token"=>"[FILTERED]",
# "user_objs"=>
# ["{\"id\":1,\"name\":\"Alex\",\"role\":\"Engineer\",\"phone\":998943333303,\"admin\":true,\"created_at\":\"2021-11-13T14:37:54.962Z\",\"updated_at\":\"2021-11-13T14:37:54.962Z\"}",
# "{\"id\":2,\"name\":\"Lucy\",\"role\":\"Accountant\",\"phone\":998943333303,\"admin\":false,\"created_at\":\"2021-11-13T14:39:52.742Z\",\"updated_at\":\"2021-11-13T14:39:52.742Z\"}"],
# "message"=>{"content"=>"Message from the browser"},
# "commit"=>"Send"}
person = JSON.parse(u)
#message = person.messages.new(message_params)
if #message.save
redirect_to root_path
else
#users = User.all
render :new
end
end
end
private
def message_params
params.permit(
:content,
:user_id
)
end
end
messages => new.html.erb
<div>
<h1>Create and send a new message!</h1>
<%= form_for(#message) do |form| %>
<% if #message.errors.any? %>
<div class="alert alert-danger">
<h5 class="fw-bold">Invalid input!</h5>
<%= #message.errors.full_messages.each do |error| %>
<div><%= error %></div>
<% end %>
</div>
<% end %>
<% #users.each do |u| %>
<div>
<p><%= check_box_tag "user_objs[]", u.to_json %> <%= u.name %></p>
</div>
<% end %>
<p class="mb-3">
<%= form.label :content, class: "form-label" %>
<%= form.text_field :content, class: "form-control", autofocus: true, placeholder: "John_D" %>
</p>
<p class="mb-3">
<%= form.submit "Send", class: "btn btn-primary" %>
</p>
<% end %>
</div>
<%= params.inspect %>
Models
# user.rb
class User < ApplicationRecord
has_many :messages
end
# message.rb
class Message < ApplicationRecord
belongs_to :user
end
Expected behavior
I was expecting the creation of messages for all selected users
Actual behavior
NoMethodError in MessagesController#create
undefined method `messages' for #<Hash:0x000000011fe2b420>
I tried different ways, but can't convert Ruby objects to JSON in my params user_objs[] so that I can parse it in my controller to create messages based on those selected users in the user_objs[] params.
Environment info
ruby -v
ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [arm64-darwin20]
rails -v
Rails 6.1.4.1
Thanks for any given help 🙏
If you want to create a system where you send a single message to multiple users you would setup a join table:
class User < ApplicationRecord
has_many :user_messages
has_many :recieved_messages, though: :user_messages,
source: :message,
inverse_of: :recipients
end
# rails g model user_message user:belongs_to message:belongs_to read:boolean
class UserMessage < ApplicationRecord
belongs_to :user
belongs_to :message
# make sure to add a compound unique index to the migration as well
validates_uniqueness_of :user_id, scope: :message_id
delegate :content, to: :message
end
class Message < ApplicationRecord
has_many :user_messages
has_many :recipients, though: :user_messages,
source: :user,
inverse_of: :recieved_messages
end
has_many :recipients will create a recipient_ids= setter and a recipient_ids getter that you can use in your form:
<div>
<h1>Create and send a new message!</h1>
<%= form_with(model: #message) do |form| %>
<% if #message.errors.any? %>
<div class="alert alert-danger">
<h5 class="fw-bold">Invalid input!</h5>
<%= #message.errors.full_messages.each do |error| %>
<div><%= error %></div>
<% end %>
</div>
<% end %>
<p class="mb-3">
<%= form.collection_checkboxes(:recipient_ids, #users, :id, :name) %>
</p>
<p class="mb-3">
<%= form.label :content, class: "form-label" %>
<%= form.text_field :content, class: "form-control", autofocus: true, placeholder: "John_D" %>
</p>
<p class="mb-3">
<%= form.submit "Send", class: "btn btn-primary" %>
</p>
<% end %>
</div>
There is absolutely no need to pass the entire record as JSON - you just pass an array of IDs and rails will do all the work of creating the join table rows for you:
class MessagesController < ApplicationController
def new
#users = User.all
#message = Message.new
end
def create
#message = Message.new(message_params)
if #message.save
redirect_to root_path
else
#users = User.all
render :new
end
end
private
def message_params
params.require(:message)
.permit(
:content,
recipient_ids: []
)
end
end
This avoids the complexity of creating multiple records from a single request and the whole conundrum that you're binding the form to a single instance of Message but creating a bunch of records which is bound to lead to confusion.
If you want to create multiple records at once it can be done but the complexity is far higher and you have to deal with stuff like how to handle errors if creating one message fails and this might be beyond your current skill level.
The issue is that you are assigning a json object/hash in person = JSON.parse(u). This is not an active record so when doing person.messages it throws the error. I believe what you need in the create action is something like:
user = JSON.parse(u)
# make sure user.inspect gives you the user object you want
person = User.find(user["id"])
# person.inspect should give you the active record for the user

Cannot create new object in rails

I am trying to create a basic Item creation in rails but I am having trouble creating new item. I want to create item's name, say Wash the dishes. These are the codes that I have:
Routes:
resources :items
ItemsController:
class ItemsController < ApplicationController
...
def new
#item = Item.new
end
def create
#item = Item.new(item_params)
if #item.save
flash[:notice] = "Item was saved!"
redirect_to #item
else
flash.now[:alert] = "ERROR. ERROR."
render :new
end
end
...
private
def item_params
params.require(:item).permit(:name, :list_id)
end
end
items/new.html.erb
<%= form_for :item do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
Lastly, schema:
create_table "items", force: :cascade do |t|
t.string "name"
t.integer "list_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
...
I got several different error codes, but this is the one I am currently stuck at (other times it showed different error code or it would simply prints out "ERROR. ERROR." (alert that I setup when save fails)
Routing Error
No route matches [POST] "/items/new"
When I go to rake routes:
POST /items(.:format) items#create
new_item GET /items/new(.:format) items#new
I followed suggestion from this SO post to check my routes and this is what I have:
2.2.2 :019 > r = Rails.application.routes
=> #<ActionDispatch::Routing::RouteSet:0x007fff323c3230>
2.2.2 :020 > r.recognize_path "/items/new"
=> {:controller=>"items", :action=>"new"}
I have also gone to rails c and I was able to create new item manually. (i = Item.new(name:"Test 123"); i.save)
What did I miss?
The problem is with your form. To understand what's wrong, do the following:
Start the rails server using rails s
Go to http://localhost:3000/items/new
Instead of filling in the form fields, view the source page
Check the form tag. Its submitting the form data to /items/new. ie.the action attribute is set to /items/new. Why is that?
From the documentation:
When the model is represented by a string or symbol, if the :url option is not specified, by default the form will be sent back to the current url (We will describe below an alternative resource-oriented usage of form_for in which the URL does not need to be specified explicitly).
<form action="/items/new" accept-charset="UTF-8" method="post">
In your routes.rb, there's no route matching POST /items/new
So, modify your form to
<%= form_for :item, url: items_path do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
This generates a form tag which posts the data to /itemsrather than /items/new.
Or replace your form with
<%= form_for #item do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
Now, the form will be submitted to /items. The advantage of using the second version is you can dry out your form for creating a new object and updating an existing object into a single view(partial).
For more, see http://guides.rubyonrails.org/form_helpers.html#binding-a-form-to-an-object
Try this in items/new.html.erb
<%= form_for #item do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>

Ruby - Saving a Model shows a Get url in browser

Ruby 2.2.4
Rails 4.2.6
PostgreSQL 9.5
I am trying to save a simple model, but when I submit the form, my browser url shows this "http://localhost:8080/notes/new?utf8=%E2%9C%93&authenticity_token=z0cyVNfUKYWDSDASDWFFZ96zj29UTtDYe8dLlKrI6Mbznb2SrTWNm%2BQ91D2s2AASD2345Fl3fTOneCC2dNg%3D%3D&note%5Btitulo%5D=ddddddd&note%5Bconteudo%5D=dddddddddddddddddd&commit=Create"
I am curious about this because other project, it has the same methods, same routes, the only difference is the model that only have one column, but it works fine.
def change
create_table :notes do |t|
t.text :titulo
t.text :conteudo
t.timestamps null: false
end
My controller: notes_controller.rb
def new
#note = Note.new
end
def create
#note = Note.new(note_params)
if #note.save
redirect_to '/'
else
render 'new'
end
end
private
def note_params
params.require(:note).permit(:titulo,:conteudo)
end
my form
<%= form_for(#note) do |f| %>
<div class="field">
<%= f.label :titulo %><br>
<%= f.text_area :titulo %>
<%= f.label :conteudo %><br>
<%= f.text_area :conteudo %>
</div>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
my routes
Rails.application.routes.draw do
root 'notes#index'
get 'notes/new' => 'notes#new'
post 'notes' => 'notes#create'
I saw this post Rails form issuing GET request instead of POST request
but does not work for me.
Edit:
I fix it thanks to Anthony E, his answer made me look back to code and realize that I have a form inside a form. The outer form was in application.html.erb.
Thanks to all.
Rails can't infer the appropriate form route from your model. Try explicitly setting the form URL and submit method in your form_for:
form_for #note, url: "/notes", as: :note, html: { method: :post } do |f|
end
Alternatively, it may be simpler to use resourceful routing:
In routes.rb:
resources :notes, only: [:new, :create, :index]
This will create the following routes:
GET /notes/new # Maps to NotesController#new
POST /notes # Maps to NotesController#create
GET /notes # Maps to NotesController#index

NoMethodError within nested model form

The project is a simple workout creator where you can add exercises to a workout plan.
I've been following the Railscast covering nested model forms to allow dynamically adding and deleting exercises, but have run into an error and need a second opinion as a new developer.
The error I am continually receiving is: NoMethodError in Plans#show
This is the extracted code, with starred line the highlighted error:
<fieldset>
**<%= link_to_add_fields "Add Exercise", f, :exercise %>**
<%= f.text_field :name %>
<%= f.number_field :weight %>
<%= f.number_field :reps %>
Note: I have the exercise model created but not an exercise controller. An exercise can only exist in a plan but I was unsure if I still needed a create action in an exercise controller for an exercise to be added?
I followed the Railscast almost verbatim (the _exercise_fields partial I slightly deviated) so you're able to view my files against the ones he has shared in the notes.
My schema.rb
create_table "exercises", force: true do |t|
t.string "name"
t.integer "weight"
t.integer "reps"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "plan_id"
end
create_table "plans", force: true do |t|
t.string "title"
t.datetime "created_at"
t.datetime "updated_at"
end
My Plan model:
class Plan < ActiveRecord::Base
has_many :exercises
accepts_nested_attributes_for :exercises, allow_destroy: true
end
My Exercise model:
class Exercise < ActiveRecord::Base
belongs_to :plan
end
My _form.html.erb
<%= form_for #plan do |f| %>
<% if #plan.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#plan.errors.count, "error") %> prohibited this plan from being saved:</h2>
<ul>
<% #plan.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<%= f.fields_for :exercises do |builder| %>
<%= render 'exercise_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Add Exercise", f, :exercises %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
My _exercise_fields.html.erb
<fieldset>
<%= link_to_add_fields "Add Exercise", f, :exercise %>
<%= f.text_field :name %>
<%= f.number_field :weight %>
<%= f.number_field :reps %>
<%= f.hidden_field :_destroy %>
<%= link_to "remove", '#', class: "remove_fields" %>
</fieldset>
My plans.js.coffee
jQuery ->
$('form').on 'click', '.remove_fields', (event) ->
$(this).prev('input[type=hidden]').val('1')
$(this).closest('fieldset').hide()
event.preventDefault()
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
My application_helper.rb
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
I'm relatively new to programming so I apologize in advance if I have easily overlooked something. Any help, suggestions, or leads for sources to read up on my issue are greatly appreciated.
Thanks!
Having implemented the functionality you seek, I'll give some ideas:
Accepts Nested Attributes For
As you already know, you can pass attributes from a parent to nested model by using the accepts_nested_attributes_for function
Although relatively simple, it's got a learning curve. So I'll explain how to use it here:
#app/models/plan.rb
Class Plan < ActiveRecord::Base
has_many :exercises
accepts_nested_attributes_for :exercises, allow_destroy: true
end
This gives the plan model the "command" to send through any extra data, if presented correctly
To send the data correctly (in Rails 4), there are several important steps:
1. Build the ActiveRecord Object
2. Use `f.fields_for` To Display The Nested Fields
3. Handle The Data With Strong Params
Build The ActiveRecord Object
#app/controllers/plans_controller.rb
def new
#plan = Plan.new
#plan.exericses.build
end
Use f.fields_for To Display Nested Fields
#app/views/plans/new.html.erb
<%= form_for #plans do |f| %>
<%= f.fields_for :exercises do |builder| %>
<%= builder.text_field :example_field %>
<% end %>
<% end %>
Handle The Data With Strong Params
#app/controllers/plans_controller.rb
def create
#plan = Plan.new(plans_params)
#plan.save
end
private
def plans_params
params.require(:plan).permit(:fields, exerices_attributes: [:extra_fields])
end
This should pass the required data to your nested model. Without this, you'll not pass the data, and your nested forms won't work at all
Appending Extra Fields
Appending extra fields is the tricky part
The problem is that generating new f.fields_for objects on the fly is only possible within a form object (which only exists in an instance)
Ryan Bates gets around this by sending the current form object through to a helper, but this causes a problem because the helper then appends the entire source code for the new field into a links' on click event (inefficient)
We found this tutorial more apt
It works like this:
Create 2 partials: f.fields_for & form partial (for ajax)
Create new route (ajax endpoint)
Create new controller action (to add extra field)
Create JS to add extra field
Create 2 Partials
#app/views/plans/add_exercise.html.erb
<%= form_for #plan, :url => plans_path, :authenticity_token => false do |f| %>
<%= render :partial => "plans/exercises_fields", locals: {f: f, child_index: Time.now.to_i} %>
<% end %>
#app/views/plans/_exercise_fields.html.erb
<%= f.fields_for :exercises, :child_index => child_index do |builder| %>
<%= builder.text_field :example %>
<% end %>
Create New Route
#config/routes.rb
resources :plans do
collection do
get :add_exercise
end
end
Create Controller Action
#app/controllers/plans_controller.rb
def add_exercise
#plan = Plan.new
#plan.exercises.build
render "add_exericse", :layout => false
end
Create JS to Add The Extra Field
#app/assets/javascripts/plans.js.coffee
$ ->
$(document).on "click", "#add_exercise", (e) ->
e.preventDefault();
#Ajax
$.ajax
url: '/messages/add_exercise'
success: (data) ->
el_to_add = $(data).html()
$('#exercises').append(el_to_add)
error: (data) ->
alert "Sorry, There Was An Error!"
Apologies for the mammoth post, but it should work & help show you more info

Rails ajax form entry shown twice

Hello I want to write a small blog with Ruby on Rails (3), with posts and comments submitted via a ajax form.
But when I submit a comment it is often shown twice, and I got no idea why.
when I write #post.comments.uniq in the _create.js.rjs file, it works fine but this seems not to be a clean solution.
When I reload the page without ajax after inserting a comment the comment is also not shown twice. Only when I insert it via ajax.
Here is the sourcecode of my project.
Blog::Application.routes.draw do
root :to => 'posts#index'
resources :posts do
resources :comments
end
end
config/routes.rb
ActiveRecord::Schema.define(:version => 20100907105618) do
create_table "comments", :force => true do |t|
t.text "text"
t.integer "post_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "posts", :force => true do |t|
t.string "title"
t.text "text"
t.datetime "created_at"
t.datetime "updated_at"
end
end
db/schema.rb
class Comment < ActiveRecord::Base
belongs_to :post
default_scope :order => "id DESC"
end
app/models/comment.rb
class Post < ActiveRecord::Base
has_many :comments
end
app/models/post.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
def show
#post = Post.find(params[:id])
end
end
app/controllers/posts_controller.rb
class CommentsController < ApplicationController
respond_to :js
def create
#post = Post.find(params[:post_id])
# if I write here p #post.comments.inspect
# it shows that there where 2 comments with the same id, how could this be?
#post.comments.create(params[:comment])
end
end
app/controllers/comments_controller.rb
<h2><%= #post.title %></h2>
<p>
<%= #post.text %>
</p>
<%= form_for [#post, Comment.new], :remote => true do |f| %>
<%= f.text_area :text, :rows => 4 %><br />
<%= f.submit "send" %>
<% end %>
<div id="comments_box">
<% if #post.comments.any? %>
<%= render :partial => #post.comments %>
<% else %>
No Comments yet
<% end %>
</div>
app/views/posts/show.html.erb
<div id="comment_<%= comment.id %>"><%= comment.text %></div>
app/views/comments/_comment.html.erb
page[:comment_text].clear
page[:comments_box].replace_html :partial => #post.comments
# ^ write here #post.comments.uniq it works
page.visual_effect(:highlight, "comment_#{#post.comments.first.id}")
app/views/comments/create.js.rjs
<% #posts.each do |post| %>
<%= link_to post.title, post %>
<% end %>
app/views/posts/index.html.erb
EDIT:
<!DOCTYPE html>
<html>
<head>
<title>Blog</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<%= yield %>
app/views/layouts/application.html.erb
I'm still not a profi in rails but you can check which js you link to your application layout file. I used to link once defaults and after my application.js and received all the ajax action twice. However I'm not sure about your case. The code you pasted looks fine.
I believe what's happening here is...
when you call
#post.comments.create(params[:comment])
Rails appends a new comment to the post. Then, when calling
:partial => #post.comments
Rails will call all of the comments from the DB that belong to this post.
We can see this in the log:
SQL (0.5ms) INSERT INTO "comments" ("created_at", "post_id", "text", "updated_at") VALUES ('2010-09-09 11:10:18.874471', 1, 'lots of pies', '2010-09-09 11:10:18.874471')
Comment Load (0.9ms) SELECT "comments".* FROM "comments" WHERE ("comments".post_id = 1) ORDER BY id DESC
Instead, try creating a new comment and then saving like so:
class CommentsController < ApplicationController
respond_to :js
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new(params[:comment])
#comment.save
end
end
And in the view:
page[:comment_text].clear
page[:comments_box].replace_html render(#post.comments)
page.visual_effect(:highlight, "comment_#{#post.comments.first.id}")
I've posted this as an example to Github
http://github.com/GavinM/Comments-Demo

Resources