rails 4 permit polymorphic association file upload - file-upload

class Picture < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
mount_uploader :file, PictureUploader
end
class PictureUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
class Ticket < ActiveRecord::Base
has_many :pictures, as: :imageable
accepts_nested_attributes_for :pictures
end
View:
h1 Add a new Ticket
= form_for #ticket do |f|
= render partial: "fields", locals: {f: f}
= f.fields_for :picture do |p|
div
= p.label :file
= p.file_field :file, :multiple => true
div
= f.submit "Create a Ticket"
Controller:
class TicketsController < ApplicationController
def create
#ticket = Ticket.new(ticket_params)
t = Logger.new(STDOUT)
t.debug "================================"
t.debug ticket_params
t.debug "================================"
#ticket.init_sp(ticket_show_path(Ticket.generate_id), request)
#ticket.errors.add(:base, "Are You Spammer?") if #ticket.spam?
if #ticket.errors.empty? and #ticket.save
redirect_to ticket_show_path(#ticket.token), notice: "Ticket created successfully. Message sent."
else
render "new"
end
end
private
def load_ticket
#ticket = Ticket.find_by_token(params[:token])
end
def ticket_params
params.require(:ticket).permit(:customer_name, :customer_email, :subject, :body, :department_id, picture_attributes: [:file])
end
end
Log:
Started POST "/tickets" for 127.0.0.1 at 2014-06-14 16:22:28 +0300
Processing by TicketsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"3ToWruQi0Pgyo1wvRz9yOib5k13yV
Vs05dXAaWA4NuM=", "ticket"=>{"customer_name"=>"Alex Drobazko", "customer_email"=
>"drobazko#gmail.com", "subject"=>"Sample Subject", "department_id"=>"", "body"=
>"Sample Body", "picture"=>{"file"=>[#<ActionDispatch::Http::UploadedFile:0x34f6
5f0 #tempfile=#<File:C:/Users/drobazko/AppData/Local/Temp/RackMultipart20140614-
8144-hj7xsk>, #original_filename="28122007(001).jpg", #content_type="image/jpeg"
, #headers="Content-Disposition: form-data; name=\"ticket[picture][file][]\"; fi
lename=\"28122007(001).jpg\"\r\nContent-Type: image/jpeg\r\n">, #<ActionDispatch
::Http::UploadedFile:0x34f65d8 #tempfile=#<File:C:/Users/drobazko/AppData/Local/
Temp/RackMultipart20140614-8144-q2595k>, #original_filename="f3.jpg", #content_t
ype="image/jpeg", #headers="Content-Disposition: form-data; name=\"ticket[pictur
e][file][]\"; filename=\"f3.jpg\"\r\nContent-Type: image/jpeg\r\n">]}}, "commit"
=>"Create a Ticket"}
Unpermitted parameters: picture
D, [2014-06-14T16:22:28.099362 #8144] DEBUG -- : ===============================
=
Unpermitted parameters: picture
D, [2014-06-14T16:22:28.103362 #8144] DEBUG -- : {"customer_name"=>"Alex Drobazk
o", "customer_email"=>"drobazko#gmail.com", "subject"=>"Sample Subject", "body"=
>"Sample Body", "department_id"=>""}
D, [2014-06-14T16:22:28.107362 #8144] DEBUG -- : ===============================
=
Problem is Nothing writes in Picture table but it does in Ticket table (see log).
Any suggestions?
EDIT: when I tried to change in View = f.fields_for :pictures do |p| to = f.fields_for :pictures do |p| but it gave me an error uninitialized constant Ticket::Picture
Also, I tried to change:
params.require(:ticket).permit(:customer_name, :customer_email, :subject, :body, :department_id, picture_attributes: [:file])
to
params.require(:ticket).permit(:customer_name, :customer_email, :subject, :body, :department_id, pictures_attributes: [:file]) (singular to plural) but without effect.
Interesting issue:
Once I restarted a server and put this in view:
= f.fields_for :pictures do |p|
The File button is disappeared on form ))

You have has_many pictures.So your ticket_params should be like this
def ticket_params
params.require(:ticket).permit(:customer_name, :customer_email, :subject, :body, :department_id, pictures_attributes: [:file])
end
Notice the plural pictures in pictures_attributes.
Update
And also this line
= f.fields_for :picture do |p|
should be like this
= f.fields_for :pictures do |p|

Related

Rails File Upload with Nested Attribute, "file_content" is blank when file gets saved

I've been following this tutorial to upload file to db Rails File Upload
.
The problem is file_contents field is blank when file gets saved and I'm uploading file with nested_attributes.
subject.rb //model
class Subject < ApplicationRecord
has_many :documents
accepts_nested_attributes_for :documents
end
document.rb //model
class Document < ApplicationRecord
belongs_to :subject
def initialize(params = {})
#file = params.delete(:file)
super
if #file
self.filename = sanitize_filename(#file.original_filename)
self.content_type = #file.content_type
self.file_contents = #file.read
end
end
private
def sanitize_filename(filename)
return File.basename(filename)
end
end
_form.html.erb //view
<%= form_for(subject) do |f| %>
.......
<div class="uk-form-row">
<%= f.fields_for :documents do |d| %>
<%= d.file_field :file, class:"uk-width-1-1 uk-form-large", placeholder:"upload document" %>
<% end %>
</div>
............
<% end %>
subjects_controller.rb
def new
#subject = Subject.new
#subject.documents.build
end
def create
#subject = Subject.new(subject_params)
respond_to do |format|
if #subject.save
format.html { redirect_to #subject, notice: 'Lesson was successfully created.' }
else
format.html { render :new }
end
end
end
private
def subject_params
params.require(:subject).permit(:title, documents_attributes:[:file])
end
Am I in the right way ? What am I missing ? I need your thoughts.
Thanks,

Rails 3.2 fields_for not showing on has_one and accepts_nested_attributes_for

I use gem 'nested_form' in rails 3.2
Models:
meeting_agenda.rb:
class MeetingAgenda < ActiveRecord::Base
has_many :meeting_questions, :inverse_of => :meeting_agenda
accepts_nested_attributes_for :meeting_questions, allow_destroy: true
attr_accessible :meeting_questions_attributes
end
meeting_question.rb:
class MeetingQuestion < ActiveRecord::Base
has_one :meeting_answer, :inverse_of => :meeting_question
accepts_nested_attributes_for :meeting_answer
attr_accessible :meeting_answer_attributes
end
meeting_answer.rb
class MeetingAnswer < ActiveRecord::Base
belongs_to :meeting_question
end
Controller:
class MeetingProtocolsController < ApplicationController
def new
#agenda = MeetingAgenda.new
rescue => error
render_403
end
def create
#agenda = MeetingAgenda.new(params[:meeting_agenda])
if #agenda.save && protocol.save
flash[:notice] = l(:notice_successful_create)
redirect_to action: 'show', id: #agenda.id
else
render action: 'new'
end
end
View:
new.html.haml:
= nested_form_for #agenda do |f|
# ...
= f.fields_for :meeting_questions do |question|
# ...
# Fields for answer is not showing!
= question.fields_for :meeting_answer do |answer| # <-- blank
%p # <-- blank
= answer.label :reporter_id_is_contact, t(:label_meeting_question_user_is_contact) # <-- blank
= answer.check_box :reporter_id_is_contact; # <-- blank
= question.link_to_remove l(:button_delete) # <-- blank
= f.link_to_add l(:button_add), :meeting_questions
= submit_tag l(:button_create)
has_one relation with accepts_nested_attributes_for is empty.
Same form working great on rails 5.

Ruby on Rails: SQL query is wrong while limiting user endorsements a month

I wanted to make that the user can endorse each user once a month and 3 users in total a month.
First of all, got a method in user model:
# Returns true if the current user is endorsing the other user.
def endorsing?(other_user)
endorsing.include?(other_user)
end
Want to slightly change it to check if the user already endorsed the user this month.
I belive it needs to look something like this:
def endorsing?(other_user)
endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).include?(other_user)
end
But that is obviously wrong cause it gives me following SQL query:
SELECT "users".* FROM "users" INNER JOIN "endorsements"
ON "users"."id" = "endorsements"."endorsed_id"
WHERE "endorsements"."endorser_id" = ? AND ("users"."created_at" BETWEEN '2016-01-01 00:00:00.000000' AND '2016-01-26 17:15:53.700307') [["endorser_id", 1]]
the "users"."created_at" should be "endorsements"."created_at"
How do I do that?
Same counts for the limit problem I've got:
def endorsement_count_within_limit?
if endorser.endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).count >= 3
errors.add(:base, "Exceeded endorse limit (3) this month")
end
end
Which gives:
SELECT COUNT(*) FROM "users" INNER JOIN "endorsements"
ON "users"."id" = "endorsements"."endorsed_id"
WHERE "endorsements"."endorser_id" = ? AND ("users"."created_at" BETWEEN '2016-01-01 00:00:00.000000' AND '2016-01-26 17:15:53.708638' [["endorser_id", 1]]
Same problem, users.created should be endorsements.created
I have no idea how to fix that, And bright ideas?
Below i'll paste my controllers, models and partials that use the methods to render forms for endorsing:
Endorsements model:
class Endorsement < ActiveRecord::Base
belongs_to :endorser, class_name: "User"
belongs_to :endorsed, class_name: "User"
validates :endorser_id, presence: true
validates :endorsed_id, presence: true
validates :comment, presence: true, length: { maximum: 140}
validate :endorsement_count_within_limit?, :on => :create
def endorsement_count_within_limit?
if endorser.endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).count >= 3
errors.add(:base, "Exceeded endorse limit (3) this month")
end
end
end
Endorsements controller:
class EndorsementsController < ApplicationController
before_action :logged_in_user
def new
end
def create
#user = User.find(params[:endorsed_id])
comment = params[:endorsement][:comment]
current_user.endorse(#user, comment)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
def destroy
#user = Endorsement.find(params[:id]).endorsed
current_user.unendorse(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
User model:
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :active_endorsements, class_name: "Endorsement",
foreign_key: "endorser_id",
dependent: :destroy
has_many :passive_endorsements, class_name: "Endorsement",
foreign_key: "endorsed_id",
dependent: :destroy
has_many :endorsing, through: :active_endorsements, source: :endorsed
has_many :endorsers, through: :passive_endorsements, source: :endorser
.
.
.
# Endorses a user.
def endorse(other_user, comment)
active_endorsements.create(endorsed_id: other_user.id, comment: comment)
end
# Unendorses a user.
def unendorse(other_user)
active_endorsements.find_by(endorsed_id: other_user.id).destroy
end
# Returns true if the current user is endorsing the other user.
def endorsing?(other_user)
endorsing.where(:created_at => (Time.zone.now.beginning_of_month..Time.zone.now)).include?(other_user)
end
private
.
.
.
end
Partials:
_endorse_form:
<% unless current_user?(#user) %>
<div id="endorse_form_<%= #user.id %>">
<% if current_user.endorsing?(#user) %>
<%= render partial: 'shared/unendorse' %>
<% else %>
<%= render partial: 'shared/endorse'%>
<% end %>
</div>
<% end %>
_endorse:
<%= form_for(current_user.active_endorsements.build, remote: true) do |f| %>
<% if f.object.endorsement_count_within_limit? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(f.object.errors.count, "error") %>.
</div>
<ul>
<% f.object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% else %>
<div><%= hidden_field_tag :endorsed_id, #user.id %></div>
<%= f.submit "Endorse", class: "btn btn-primary" %>
<%= f.text_field :comment, class: 'form-control' %>
<% end %>
<% end %>
_unendorse:
<%= form_for(current_user.active_endorsements.find_by(endorsed_id: #user.id),
html: { method: :delete },
remote: true) do |f| %>
<%= f.submit "Remove endorse", class: "btn" %>
<% end %>
If you need to look at other files it's available here with some parts missing:https://bitbucket.org/kramarz/pracainzynierska
try explicitly giving the columns for endorsments in your where statement
endorsing.where(%{
endorsments.created_at BETWEEN
'#{Time.zone.now.beginning_of_month.to_s(:db)}'
AND
'#{Time.zone.now.to_s(:db)}'
})
At first glance it looks like you need to specify active_endorsements or passive_endorsements where your examples are using endorsing, which references the endorsed user through the Endorsement.
This would all be a little less convoluted if the names were changed to better reflect what each object is. Of course, naming is much more difficult than it seems.
You might have better luck restructuring your relations like so:
class User < ActiveRecord::Base
has_many :outbound_endorsements, class_name: "Endorsement", foreign_key: "endorser_id", dependent: :destroy
has_many :inbound_endorsements, class_name: "Endorsement", foreign_key: "endorsed_id", dependent: :destroy
has_many :endorsed_users, through: :outbound_endorsements, source: :endorsed_user
has_many :endorsing_users, through: :inbound_endorsements, source: :endorsing_user
end
class Endorsement < ActiveRecord::Base
belongs_to :endorsing_user, class_name: "User"
belongs_to :endorsed_user, class_name: "User"
scope :current, -> { where(created_at: (Time.zone.now.beginning_of_month..Time.zone.now) }
end
As for the validation, this is more complex than validating data to be persisted by any single model. In these cases, it's really useful to put this logic in another object entirely:
class ValidatedEndorsement
attr_reader :endorsing_user, :endorsed_user
def initialize(endorsing_user, endorsed_user)
#endorsing_user, #endorsed_user = endorsing_user, endorsed_user
end
def valid?
# note: User -> Endorsement -> Scope -> Count = violation of the
# law of demeter; if this works well, refactor appropriately ;)
if #endorsing_user.outbound_endorsements.current.count >= 3
# no dice
return false
end
true
end
end
If this object is going to be used in response to a user request, it might be worth your while to consider making this object a FormObject that uses the underlying validations of an ActiveRecord model. This is easy to do with virtus, requiring only a few lines to change in the above ValidatedEndorsement.

Polymorphic Comments with Ancestry Problems

I am trying to roll together two Railscasts: http://railscasts.com/episodes/262-trees-with-ancestry and http://railscasts.com/episodes/154-polymorphic-association on my app.
My Models:
class Location < ActiveRecord::Base
has_many :comments, :as => :commentable, :dependent => :destroy
end
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
My Controllers:
class LocationsController < ApplicationController
def show
#location = Location.find(params[:id])
#comments = #location.comments.arrange(:order => :created_at)
respond_to do |format|
format.html # show.html.erb
format.json { render json: #location }
end
end
end
class CommentsController < InheritedResources::Base
def index
#commentable = find_commentable
#comments = #commentable.comments.where(:company_id => session[:company_id])
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
#comment.user_id = session[:user_id]
#comment.company_id = session[:company_id]
if #comment.save
flash[:notice] = "Successfully created comment."
redirect_to :id => nil
else
render :action => 'new'
end
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
In my locations show view I have this code:
<%= render #comments %>
<%= render "comments/form" %>
Which outputs properly. I have a _comment.html.erb file that renders each comment etc. and a _form.html.erb file that creates the form for a new comment.
The problem I have is that when I try <%= nested_comments #comments %> I get undefined method 'arrange'.
I did some Googling and the common solution to this was to add subtree before the arrange but that throws and undefined error also. I am guessing the polymorphic association is the problem here but I am at a loss as to how to fix it.
Dumb mistake... forgot to add the ancestry gem and required migration which I thought I had already done. The last place I checked was my model where I eventually discovered my error.

fields_for not working on update

I think this is a stupid mistake… When i create a record, my "resources" and "page_settings" tables getting populated.
But my "page_setting" does nothing when i try to update the record.
My models:
class Resource < ActiveRecord::Base
has_one :page_setting
accepts_nested_attributes_for :page_setting
end
class PageSetting < ActiveRecord::Base
belongs_to :resource
end
Here is the resources controller:
class ResourcesController < ApplicationController
# Initialize resource and belonging type model
before_filter :build_resource_and_type, :only => [:new, :create]
before_filter :get_resource_and_type, :only => [:edit, :update]
def new
end
def create
if #resource.save
flash[:notice] = "Resource wurde erstellt"
redirect_to root_url
else
flash[:error] = "Resource konnte nicht erstellt werden"
render :action => 'new'
end
end
def edit
end
def update
if #resource.update_attributes(params[:resource])
flash[:notice] = "#{#type_name} #{#resource.title} wurde aktualisiert"
redirect_to root_url
else
flash[:error] = "#{#type_name} #{#resource.title} konnte nicht aktualisiert werden"
render :action => 'edit'
end
end
private
def build_resource_and_type
# Get type from URL param (new action) or hidden field param (create action)
type = params[:type_name] || params[:resource][:type_name]
#resource = current_user.microsite.resources.new(params[:resource])
#resource.type_name = type
# Build belonging model depending on type param
case type
when 'page'
#resource.build_page_setting(params[:page_setting])
#type_name = 'page'
end
end
def get_resource_and_type
#resource = current_user.microsite.resources.find(params[:id])
#type_name = #resource.type_name
end
end
And the essential part of my resource form:
<%= form_for #resource do |resource_form| %>
<%= resource_form.hidden_field :type_name, :value => #type_name %>
…
<%= fields_for #resource.page_setting do |page_form| %>
<%= page_form.label :content, "Text" %>
<%= page_form.text_area :content %>
<% end %>
<% end %>
You have to make a small change in Your resource form:
<%= f.fields_for :page_setting, #resource.page_setting do |page_form| %>
Then it should work, like You want to.