In my (very first) rails app, I have a model called "Annotations"; this will take some generic data as well as an attachment (PDF). Next, I need to the ability to do the actual annotations to this attachment/PDF ("annotate") and store the results on a field on the "Annotations" model (as a JSON?).
Currently, I think I should create a new method "annotate" in the AnnotationsController (needs to update the annotations object) and call a new view called "annotate.html.erb".
Any advice how to go about "the rails way" ?
Update
meanwhile, I have:
model (annotation.rb)
class Annotation < ApplicationRecord
has_many :comments, dependent: :destroy
belongs_to :documenttype
has_attached_file :file, styles: { large: "600x600>", medium: "500x500>", thumb: "150x150#" }, default_url: "/images/:style/missing.png"
accepts_nested_attributes_for :documenttype
validates_attachment_content_type :file, content_type: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf']
validates :name, presence: true, uniqueness: true, length: { minimum: 10, maximum: 50 }
validates :description, length: { minimum: 20, maximum: 500 }
validates :documenttype, presence: true
validates :file, presence: true
end
routes
Rails.application.routes.draw do
root 'dashboard#index'
devise_for :users
resources :users,:documenttypes, :documents
resources :annotations do
resources :comments
end
get "annotate", to: "annotations#annotate"
the controller (AnnotationsController)
class AnnotationsController < ApplicationController
before_action :annotate, only: [:edit, :update ]
def index
#annotations = Annotation.all
end
def show
#annotation = Annotation.find(params[:id])
end
def new
#annotation = Annotation.new
end
def edit
#annotation = Annotation.find(params[:id])
end
def create
#annotation = Annotation.new(annotation_params)
if #annotation.save
redirect_to #annotation
else
render 'new'
end
end
def update
#annotation = Annotation.find(params[:id])
if #annotation.update(annotation_params)
redirect_to #annotation
else
render 'edit'
end
end
def destroy
#annotation = Annotation.find(params[:id])
#annotation.destroy
redirect_to annotations_path
end
private
def annotate
#annotation = Annotation.find(params[:id])
end
def annotation_params
params.require(:annotation).permit(:name, :description, :file, :active, :documenttype_id)
end
end
views that render 1 form (using simple_form)
<div class="container-fluid">
<div class="row">
<h4>Annotation</h4>
<div class="col-md-6">
<%= simple_form_for #annotation, html: { class: 'form-horizontal', multipart: true },
wrapper: :horizontal_form,
wrapper_mappings: {
check_boxes: :horizontal_radio_and_checkboxes,
radio_buttons: :horizontal_radio_and_checkboxes,
file: :horizontal_file_input,
boolean: :horizontal_boolean
} do |f| %>
<%= f.error_notification %>
<% if #annotation.file.blank? %>
<%= f.input :file, as: :file, input_html: { accept: ('application/pdf') } %>
<% else %>
<% end -%>
<%= f.input :name, placeholder: 'Enter name' %>
<%= f.input :description, placeholder: 'Description' %>
<%= f.association :documenttype %>
<%= f.input :active, as: :boolean %>
<%= f.button :submit %>
<% unless #annotation.file.blank? %>
<%= link_to ' Annotate', annotate_path(#annotation), :class => "btn btn-default" %>
<% end -%>
<% end -%>
<p><br><%= link_to 'List' , annotations_path %></p>
</div>
<% unless #annotation.file.blank? %>
<div class="col-md-6">
<p><strong>File name: </strong><%= #annotation.file_file_name %></p>
<iframe src="<%= #annotation.file %>" width=100% height=450px class="img-rounded"></iframe>
</div>
<% end %>
</div>
<% unless #annotation.new_record? %>
<div class="row">
<hr>
<div class="col-md-6">
<%= render #annotation.comments %>
</div>
<div class="col-md-6">
<%= render 'comments/form' %>
</div>
</div>
<% end -%>
</div>
Also, I created a view call annotate.html.erb
It is called now as static page under http://localhost:3000/annotate ; while I think it should be under http://localhost:3000/annotations/annotate/:id -- so looks like a routing issue (for) now. (Routing still is a bit of a mystery to me :-) )
If you want to do it the rails way:
Your model should be singular, so: Annotation.rb:
class Annotation < ApplicationRecord
end
Your controller should be called AnnotationsController and have the standard CRUD methods: new and create (to create a new annotation). edit and update to update an annotation and destroy to destroy your annotations.
SOMETHING LIKE:
class AnnotationsController < ApplicationController
before_action :set_annotation, only: [:edit, :update, :destroy]
def index
#annotations = Annotation.all
end
def new
#annotation = Annotation.new
end
def create
#annotation = Annotation.new(annotation_params)
if #annotation.save
redirect_to annotations_path, notice: "annotations created"
else
render action: 'new'
end
end
def edit
end
def destroy
#annotation.destroy
redirect_to annotations_path
end
def update
if #annotation.update(annotation_params)
redirect_to annotations_path
else
render action: 'edit'
end
end
private
def set_annotation
#annotation = Annotation.find(params[:id])
end
def annotation_params
params.require(:annotation).permit(:name, ...)
end
end
Your views should live in views/annotations/
Since this is your first rails app, I suggest you use the scaffolding option to build your app, which according to the documetation is:
A scaffold in Rails is a full set of model, database migration for
that model, controller to manipulate it, views to view and manipulate
the data, and a test suite for each of the above.
rails g scaffold Annotate
see: http://guides.rubyonrails.org/command_line.html
Found the routing get "annotations/:id/annotate" => "annotations#annotate", as: 'annotate'
And the set_annotation method should not be private.
May be there is a better routing (always welcome), but it works now.
Related
I'm trying to show error messages when creating a new instance of a model in my Rails app when a field doesn't pass validation. For some reason, the errors never actually show up on the website next to the fields like they're supposed to. However, the errors appear in the 'Preview' section of the Network tab of Chrome DevTools. So the errors are generating properly. In the terminal it says that new.html.erb is rendered but I don't think it actually does? Any help would be greatly appreciated - I haven't found much about this online. I'm using Tailwind CSS for styling the front end if that's helpful.
Here's my code:
occasion.rb
class Occasion < ApplicationRecord
belongs_to :user
has_many :registries
has_many :charities, :through => :registries
validates :occasion_name, presence: true
validates :occasion_date, presence: true
validates :url, presence: true, format: { without: /\s/ }, uniqueness: true
validates_uniqueness_of :user_id
end
occasions_controller.rb
class OccasionsController < ApplicationController
load_and_authorize_resource only: [:new, :create, :edit, :update, :destroy]
def index
#occasions = Occasion.all
end
def show
#occasion = Occasion.where(url: params[:url]).first
end
def new
#occasion = Occasion.new
end
def create
#occasion = Occasion.new(occasion_params)
#occasion.user = current_user
if #occasion.save
respond_to do |format|
format.html { redirect_to new_registry_path }
format.js { render :js => "window.location='#{ new_registry_path }'" }
end
else
render :new
end
end
def edit
#occasion = Occasion.find(params[:id])
end
def update
#occasion = Occasion.find(params[:id])
if #occasion.update(occasion_params)
redirect_to #occasion
return
else
render :edit
end
end
def destroy
#occasion = Occasion.find(params[:id])
#occasion.destroy
redirect_to occasions_path
end
private
def occasion_params
params.require(:occasion).permit(:user_id, :occasion_name, :occasion_date, :url)
end
# user authentication is not required for show
skip_before_action :authenticate_user!, :only => [:show]
end
new.html.erb
<%= form_with model: #occasion do |form| %>
<div class="text-center">
<%= form.label :occasion_name, "Occasion Name", class: "text-red-400 font-semibold px-8" %><br>
<%= form.text_field :occasion_name, class: "rounded w-2/5" %>
<% #occasion.errors.full_messages_for(:occasion_name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div class="text-center py-2">
<%= form.label :occasion_date, "Occasion Date", class: "text-red-400 font-semibold px-8" %><br>
<%= form.date_field :occasion_date, type: "date", class: "rounded" %>
<% #occasion.errors.full_messages_for(:occasion_date).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div class="text-center py-2">
<%= form.label :url, 'URL', class: "text-red-400 font-semibold px-8" %><br>
<%= form.text_field :url, class: "rounded" %>
<% #occasion.errors.full_messages_for(:url).each do |message| %>
<div><%= message %></div>
<% end %>
<em><div class="text-sm">domainname.com/yourURLhere</div></em>
</div>
<div class="text-center py-2">
<%= form.submit occasion.persisted? ? 'Update' : 'Save', class: "rounded-full bg-red-400 text-white px-3" %>
</div>
<% end %>
From the provided information, it looks like the form gets submitted as an AJAX request. Since you're not passing local: false to the form_with call, there must be a configuration set to use AJAX form submissions by default.
From the docs,
:local - By default form submits via typical HTTP requests. Enable remote and unobtrusive XHRs submits with local: false. Remote forms may be enabled by default by setting config.action_view.form_with_generates_remote_forms = true.
Pass local: true to submit the request via a normal HTTP request.
<%= form_with model: #occasion, local: true do |form| %>
<%#= ... %>
<% end %>
I've already looked through every other stackoverflow for this issue, but none of the solutions have fixed this. My elements in a nested_form are not being saved in the database. I've also made sure that all model associations are correct. I've been trying to fix this for nearly 8 hours now, and would really appreciate some help, especially considering every other solution hasn't worked.
Basically, I have a Playlist model that contains multiple Song models. I'm trying to use a nested_form to add the Song models to the Playlist. However, none of the Songs are ever being saved. I apologize if my methods are misguides, as I'm still fairly new to Rails.
GitHub Repo:https://github.com/nsalesky/Ultra-Music
playlists_controller.rb
def index
#user = current_user
#playlists = #user.playlists
end
def show
#user = current_user
#playlist = #user.playlists.find(params[:id])
end
def new
#playlist = Playlist.new
#I was told to do this
#playlist.songs.build
end
def create
#user = current_user
#playlist = #user.playlists.create(playlist_params)
if #playlist.save
redirect_to #playlist
else
render :action => 'new'
end
end
def edit
#playlist = current_user.playlists.find(params[:id])
end
def update
#user = current_user
#playlist = #user.playlists.find(params[:id])
if #playlist.update_attributes(playlist_params)
redirect_to #playlist
else
render :action => 'edit'
end
end
def destroy
#user = current_user
#playlist = #user.playlists.find(params[:id])
#playlist.destroy
redirect_to playlists_path(#user.playlists)
end
private
def playlist_params
params.require(:playlist).permit(:name, :description, songs_attributes: [:id, :name, :link, :_destroy])
end
playlist.rb
belongs_to :user
has_many :songs, dependent: :destroy
accepts_nested_attributes_for :songs, :allow_destroy => true, :reject_if => lambda { |a| a[:content].blank? }
validates :name, presence: true
validates_associated :songs, presence: true
_form.html.erb
<%= nested_form_for #playlist do |f| %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<!--<div>
<button type="button" id="addsong">Add Song</button><br>
<button type="button" id="removesong">Remove Song</button><br>
</div> !-->
<div>
<%= f.fields_for :songs do |song_form| %>
<%= song_form.text_field :name %>
<%= song_form.text_field :link %>
<%= song_form.link_to_remove "Remove Song" %>
<% end %>
<p><%= f.link_to_add "Add Song", :songs %></p>
</div>
<div>
<%= f.submit %>
</div>
<% end %>
In your playlist.rb, you wrote:
:reject_if => lambda { |a| a[:content].blank? }
Here the block parameter |a| stands for attributes of a specific song. So a[:attribute] relates to a single attribute. The problem is your Song doesn't have a :content attribute. So this a[:content].blank? will always be true, means you would be rejected building a song.
Just change a[:content] to a valid attribute such as a[:name]
I am trying to update an invoice fields, when checking out in the carts controller. These must be present when checking out, or it should fail. However, I can't get it to update, much less validate them.
Here is my code:
cart show view:
<div class = "row">
<div class = "col-lg-3 col-lg-offset-6 text-left">
<strong>Customer: </strong>
<%= collection_select(:invoice, :customer_id, #customers, :id, :full_name, {:prompt => 'Please Select'}, class: 'form-control') %>
</div>
<div class = "col-lg-3 ext-left">
<strong>Seller: </strong>
<%= collection_select(:invoice, :employee_id, #employees, :id, :full_name, {:prompt => 'Please Select'}, class: 'form-control') %>
</div>
<div class = "col-lg-12 text-right">
<%= form_tag carts_checkout_path, method: :post do |f| %>
<%= submit_tag 'Complete', class: 'btn btn-success' %>
<% end %>
</div>
</div>
carts controller:
class CartsController < ApplicationController
def show
#invoice = current_invoice
#invoice_products = current_invoice.invoice_products
#customers = Customer.all
#employees = Employee.all
end
def checkout
current_invoice.customer_id = params[:customer_id]
current_invoice.employee_id = params[:employee_id]
current_invoice.save
redirect_to current_invoice
end
end
current_invoice is the current session's invoice, related to the cart. It redirects correctly, but doesn't update.
in the invoices controller:
def invoice_params
params.require(:invoice).permit(:invoice_number, :customer_id, :invoice_date, :invoice_status_id, :employee_id, invoice_products_attributes: [:id, :invoice_id, :product_id, :price, :tax, :discount, :value])
end
Can anyone please help me in identifying where I am going wrong? Could it be my approach is not even valid?
Thanks in advance
The type of functionality you're after is considered "business logic" and should be implemented in the model and called from the controller.
You can define a method in a model:
class Invoice < ActiveRecord::Base
def update_invoice(cust_id, emp_id)
if self.update_attributes(:customer_id => cust_id], :employee_id = emp_id])
puts "Success!
else
puts "Failed to update record. Handle the error."
end
end
You can call my_method from carts_controller.rb like this:
def update
# all your regular update logic here
# replace the bit of code that saves the cart with something like this:
respond_to do |format|
if(current_invoice.update_invoice(params[:customer_id], params[:employee_id])
if(#cart.update(cart_params))
format.html { redirect_to #activity, notice: 'Activity was successfully updated.' }
format.json { render :show, status: :ok, location: #activity }
else
format.html { render :edit }
format.json { render json: #activity.errors, status: :unprocessable_entity }
end
end
end
Also, note the use of update_attributes rather than save. Bear in mind that update_attributes will return false if you run into any problems updating (e.g. one or more validations failed). Don't confuse update_attributes with the singular update_attribute which updates a single field and will not run validations.
Finally got it.
current_invoice.update_attributes(customer_id: params[:invoice][:customer_id], employee_id: params[:invoice][:employee_id])
Also in view, changed location of form_tag:
<div class = "row">
<%= form_tag carts_checkout_path, method: :post do |f| %>
<div class = "col-lg-3 col-lg-offset-6 text-left">
<strong>Cliente: </strong>
<%= collection_select(:invoice, :customer_id, #customers, :id, :full_name, {:prompt => 'Favor Seleccionar'}, class: 'form-control') %>
</div>
<div class = "col-lg-3 ext-left">
<strong>Vendedor: </strong>
<%= collection_select(:invoice, :employee_id, #employees, :id, :full_name, {:prompt => 'Favor Seleccionar'}, class: 'form-control') %>
</div>
<div class = "col-lg-12 text-right">
<%= submit_tag 'Completar', class: 'btn btn-success' %>
</div>
<% end %>
</div>
Could it be my approach is not even valid
Your approach is definitely valid, it's great that you're using sessions in this way.
I'd do it slightly differently:
#config/routes.rb
resource :cart, except: [:edit, :new, :create], path_names: { update: "checkout" }
This will give you the following paths:
#url.com/cart -> carts#show (here you can invoke a cart if one doesn't exist)
#url.com/cart/checkout #-> POST to "update" method in carts controller
#url.com/cart/ (method: :delete) -> DELETE to "destroy" cart (refresh)
--
#app/controllers/carts_controller.rb
class CartsController < ApplicationController
before_action :setup_cart
def show
#cart = current_cart #-> products called from this. I don't know how you're linking them
#customers = Customer.all
#employees = Employee.all
end
def update
#invoice = Invoice.find_or_create_by(id: current_card.id)
#invoice.update update_params
redirect_to cart_path
end
def destroy
current_cart = nil
redirect_to carts_path, notice: "Cart Cleared"
end
private
def setup_cart
current_cart ||= sessions[:cart]
end
def update_params
params.require(:cart).permit(:customer_id, :employee_id)
end
end
Now, to update the cart, you'll want to take note from MarsAtomic's answer. However it must be noted that naked params are not available in the model.
If you use update_attributes, or just plain update, you'll need to do the following:
#app/models/cart.rb
class Invoice < ActiveRecord::Base
has_many :products
belongs_to :employee
belongs_to :customer
#validations here
#callbacks here (what MarsAtomic refers to as business logic)
before_save :do_something, only: :update
private
def do_something
#something here
#params appended to current instance of object
#eg self.customer_id
end
end
I'd also go more succinct in your view:
#app/views/carts/show.html.erb
<div class = "row">
<%= form_tag cart_checkout_path, method: :patch do |f| %>
<% options = [["cliente", "customer"], ["vendedor", "employee"]] %>
<% options.each do |name, type| %>
<%= content_tag :strong, "#{name.titleize}:" %>
<%= collection_select :cart, eval(":#{type}_id"), instance_variable_get("##{type.pluralize}"), :id, :full_name, {:prompt => 'Favor Seleccionar'}, class: 'form-control') %>
<% end %>
<% content_tag :div, class: "col-lg-12 text-right" do %>
<%= submit_tag 'Completar', class: 'btn btn-success' %>
<% end %>
<% end %>
</div>
I am building a Rails app where I need users to upload a profile image on the registration form/have an image edit feature for profile photo. Carrierwave should be properly installed and so far Devise seems to be running fine. When I go to upload the photo and then click "Create Profile", it simply refreshes to the same page and resets the file upload field each time.
SOLUTION: Updated update method in controller to:
def update
#pet = Pet.find(params[:id])
if #pet.update(pet_params)
redirect_to pets_path
else
render :edit
end
end
It's uploaded in my app/uploaders/profile_photo_uploader.rb file:
class ProfilePhotoUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
I have a "profile_photo" string in my schema, as well.
And my controller actions for create and edit/update are here:
def create
#pet = Pet.new(pet_params)
if #pet.save
flash[:notice] = 'Your pet profile was saved!'
redirect_to pets_path
else
flash.now[:notice] = 'Your pet profile could not be saved.'
render :new
end
end
def show
#pet = Pet.find(params[:id])
end
def edit
#pet = Pet.find(params[:id])
end
def update
#pet = Pet.find(params[:id])
if #pet.save
redirect_to pets_path
else
render :edit
end
end
def pet_params
params.require(:pet).permit(:name, :breed, :age,
:color, :weight, :personality, :favorite_things, :owner_id, :profile_photo)
end
My new form using SimpleForm:
<h1>New Pet Profile</h1>
<%= simple_form_for #pet, html: { multipart: true } do |f| %>
<%= f.input :name, required: true, label: "Pet Name:" %>
<%= f.input :profile_photo, type: :file %>
...
<%= f.button :submit %>
<% end %>
Routes:
Rails.application.routes.draw do
devise_for :owners
resources :pets
resources :owners do
resources :pets, shallow: true
end
end
Application Controller:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :profile_photo
end
end
You need to have your model set app/models/pet.rb with
class Pet < ActiveRecord::Base
validates_presence_of :profile_photo
belongs_to :owner
mount_uploader :profile_photo, ProfilePhotoUploader
end
Also, side note, if you're using Heroku it won't save pictures since it's a static service. You need to integrate a service like Cloudinary with Carrierwave.
After your form line <%= simple_form_for #pet, html: { multipart: true } do |f| %> put this in
<% if #pet.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#pet.errors.count, "error") %> prohibited this pet from being saved:</h2>
<ul>
<% #pet.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
and this line <%= f.input :profile_photo, type: :file %> to this <%= f.file_field :profile_photo %>
At the end of your pet_controller's file make sure you've given permission for the form to submit the data
def pet_params
params.require(:pet).permit(:name, :profile_photo)
end
Since I see you're using devise you might consider assigning the owner to the pet. Consider the line in your pet_controller.rb
def create
#pet = Pet.new(pet_params)
#pet.owner = current_user
And see the line in model belongs_to :owner
I think I have a working version of acts_as_commenting_with_threading in my rails app, but it seems like the body of every comment is saved with weird formatting. How do I remove the formatting in my view so it only displays the text (and not the formatting)? For example, if I type the text "test comment," the body of the comment is saved as "---\nbody: test comment\n". I tried html_safe, but it didn't work.
step.rb
class Step < ActiveRecord::Base
extend FriendlyId
acts_as_commentable
friendly_id :position
has_ancestry :orphan_strategy => :adopt
attr_accessible :description, :name, :position, :project_id, :images_attributes, :parent_id, :ancestry, :published_on
belongs_to :project
has_many :images, :dependent => :destroy
accepts_nested_attributes_for :images, :allow_destroy => :true
validates :name, :presence => true
end
comments_controller.rb
class CommentsController < ApplicationController
def create
#project = Project.find(params[:project_id])
#commentText = params[:comment]
#user = current_user
#comment = Comment.build_from(#project.steps.find(params[:step_id]), #user.id, #commentText)
respond_to do |format|
if #comment.save
format.html {redirect_to :back}
else
format.html { render :action => 'new' }
end
end
end
end
show.html.erb:
<div class="stepComments">
<% if step.comment_threads.count >0 %>
<% step.comment_threads.each do |stepComment| %>
<% if stepComment.body.length>0 %>
<%= render :partial => 'comments', :locals => {:comment=> stepComment} %>
<% end %>
<br>
<% end %>
<% end %>
</div>
_comments.html.erb
<div class="comment">
<div class="userIcon">
<%= User.find(comment.user_id).username %>
<%= image_tag(User.where(:id=>comment.user_id).first.avatar_url(:thumb), :class=>"commentAvatar img-polaroid")%>
</div>
<div class="field">
<%= comment.body %>
</div>
</div>
This prints: "---\nbody: test comment\n"
The rails helper simple_format will print using the formatting rules so you will get just the text.
For example, <% simple_format(comment.body) %>
I couldn't figure out a way to do it besides just edited the string manually. This is what I ended up using:
<%= comment.body.slice((comment.body.index(' ')+1..comment.body.length)) %>
It seems very odd that there isn't some built in function for doing this...
It ended up being a quite simple solution; I had been calling the parameter incorrectly. It should have been:
#commentText = params[:comment][:body]