Is it possible to prevent empty action text entries - ruby-on-rails

I have a very simple action text model and form
class Course < ApplicationRecord
validates :title, presence: true
has_rich_text :content
end
<%= form_with model: #course do |f| %>
<%= f.text_field :title %>
<%= f.rich_text_area :content %>
<% end %>
It's all working great but since the content field is optional is it possible to create a course model without creating action_text_rich_texts entries that are empty/blank? Even if the user only enters the title without any content it's currently creating them and there's a lot of unnecessary and empty action_text_rich_texts rows in the database

The way I handled this in my application is with a before_save callback that removes the ActionText::RichText database record if the body is blank.
This avoids polluting the controller and works on both create and update actions. The body attribute of the action_text attribute is still accessible even without a corresponding database record, because ActionText will instantiate a new object if the record cannot be found (which allows you to test for blank? in either scenario).
Try this:
class Course < ApplicationRecord
validates :title, presence: true
has_rich_text :content
before_save :clean_up_content
private
def clean_up_content
self.content.destroy if self.content.body.blank?
end
end

I'm not sure about anything built into Actiontext for this, but I would imagine you could handle this at the controller level.
The first thing I would try is to see if not setting anything to content prevents Rails from creating an associated record:
class CourseController
def create
# remove course_params[:content] if it's blank
course_values = course_params[:content].blank? ? course_params.except(:content) : course_params
Course.create(course_values)
...
end
end

Extending Eric Powell's approach:
# app/models/concerns/do_not_save_blank_rich_text.rb
module DoNotSaveBlankRichText
extend ActiveSupport::Concern
included do
before_validation :do_not_save_blank_rich_text
end
private
def do_not_save_blank_rich_text
rich_text_attributes = self.class.reflections.values.select do |reflection|
reflection.options[:class_name] == "ActionText::RichText"
end.map(&:name)
rich_text_attributes.each do |rich_text_attribute|
if self.public_send(rich_text_attribute) && self.public_send(rich_text_attribute).body.blank?
self.public_send(rich_text_attribute).mark_for_destruction
end
end
end
end

Related

When to use attr:accessors in place of a permanent column in rails?

I'm creating my own website using Ruby on Rails. One thing that I've failed to comprehend is why and when to use attr:accessors in place of a permanent column for a model. For instance, let's say that I created a 'posts' model which would have a title, description and some content associated with it. Now should I do rails g model Post title:string description:text content:text or should I declare them as attr:accessible :title, :description, :content.
I'm not very experienced in rails, so please bear with me if this sounds too silly to you.
You can use attr_accessor if you need virtual attributes in model.
For eg: In contact us form you need not to see form data, but you need to send that data using email. So you can create attr_accessor for adding virtual attributes and can also apply validations on that.
class Contact
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
ref
attr_accessible is to white list of attributes that can be mass assigned in model.
class Comment < ActiveRecord::Base
attr_accessible :user_id, :content
end
def create
#so here params[:comment], have all parameters. But if those params are not in attr_accessible list it will not save it.
# you can try that by remove attr_accessible from model
#comment = Comment.new(params[:comment])
if #comment.save
flash[:notice] = "Successfully created comment."
redirect_to #comment
else
render :action => 'new'
end
end
Comment Form:
<%= form_for #comment do |f| %>
<%= f.error_messages %>
<%= f.hidden_field :user_id, value: current_user.id %>
<p>
<%= f.label :content %><br />
<%= f.text_area :content %>
</p>
<p><%= f.submit %></p>
<% end %>
Happy Coding...
To add to Pardeep's epic answer, you'll want to look at this RailsCast (RE "virtual attributes"):
attr_accessor basically creates a setter & getter method in the model.
Probably doesn't make any sense; what you have to remember is that each Rails model is a class. Classes form the backbone of object-orientated programming.
Since Ruby is object orientated, each time you do anything with the language, it expects classes to be invoked & manipulated. The basis of OOP is to load classes into memory & play with them; good write-up here.
In classic OOP, your classes would be hard-coded with a series of attributes:
class Mario
def jump
pos_y + 5
end
def pos_y
# gets y val from the viewport
end
end
This will allow you to send instructions to the program, in turn modifying the class:
#mario.jump
... this should modify the viewport etc in the way you defined within the class.
--
Rails is very similar to the above, except most of the attributes are defined by ActiveRecord;
#app/models/mario.rb
class Mario < ActiveRecord::Base
# attributes from db include height, weight, color etc
end
Rails models allow you to call:
#mario = Mario.find x
#mario.height = "255"
... however, they don't allow you to create attributes which are stored in memory only.
For example...
#app/models/mario.rb
class Mario < ActiveRecord::Base
attr_accessor :grown
end
The above will give you an instance value of grown, which will allow you to populate this independently of the database.
So say you wanted to...
#mario = Mario.find x
#mario.grown = true if params[:grown]
#mario.height += "150" if #mario.grown
Regarding the difference between attr_accessor and attr_accessible, you'll want to look up Rails 3 and mass assignment.
I came into Rails ~ 4.0, so I didn't have to deal with attr_accessible so much; it was basically the way to permit parameters in Rails 3 models.
In Rails 4/5, you use strong params in the controller:
#app/controllers/mario_controller.rb
class MarioController < ApplicationController
def create
#mario = Mario.new mario_params
#mario.save
end
private
def mario_params
params.require(:mario).permit(:x, :y, :z)
end
end

Can't mass-assign protected attributes for creating a has_many nested model with Devise

I've watched the RailsCast, another nested attributes video, lots of SO posts, and fought with this for a while, but I still can't figure it out. I hope it's something tiny.
I have two models, User (created by Devise), and Locker (aka, a product wishlist), and I'm trying to create a Locker for a User when they sign up. My login form has a field for the name of their new Locker (aptly called :name) that I'm trying to assign to the locker that gets created upon new user registration. All I'm ever greeted with is:
WARNING: Can't mass-assign protected attributes: locker
I've tried every combination of accepts_nested_attributes and attr_accesible in both of my models, yet still nothing works. I can see from the logs that it's being processed by the Devise#create method, and I know Devise isn't smart enough to create my models how I want :)
Here's the relevant bits of my two models:
# user.rb
class User < ActiveRecord::Base
attr_accessible :username, :email, :password, :password_confirmation, :remember_me, :locker_attributes
# Associations
has_many :lockers
has_many :lockups, :through => :lockers
# Model nesting access
accepts_nested_attributes_for :lockers
end
and
# locker.rb
class Locker < ActiveRecord::Base
belongs_to :user
has_many :lockups
has_many :products, :through => :lockups
attr_accessible :name, :description
end
# lockers_controller.rb (create)
#locker = current_user.lockers.build(params[:locker])
#locker.save
I'm assuming I need to override Devise's create method to somehow get this to work, but I'm quite new to rails and am getting used to the black box "magic" nature of it all.
If anyone can help me out, I'd be incredibly thankful. Already spent too much time on this as it is :)
EDIT: I realized I omitted something in my problem. My Locker model has three attributes - name, description (not mandatory), and user_id to link it back to the User. My signup form only requires the name, so I'm not looping through all the attributes in my nested form. Could that have something to do with my issue too?
EDIT 2: I also figured out how to override Devise's RegistrationsController#create method, I just don't know what to put there. Devise's whole resource thing doesn't make sense to me, and browsing their source code for the RegistrationsController didn't help me much either.
And for bonus points: When a user submits the login form with invalid data, the Locker field always comes back blank, while the regular Devise fields, username & email, are filled in. Could this also be fixed easily? If so, how?
first, you have a typo :
attr_accessible :locker_attributes
should be plural :
attr_accessible :lockers_attributes
then, the standard way to use nested_attributes is :
<%= form_for #user do |f| %>
<%# fields_for will iterate over all user.lockers and
build fields for each one of them using the block below,
with html name attributes like user[lockers_attributes][0][name].
it will also generate a hidden field user[lockers_attributes][0][id]
if the locker is already persisted, which allows nested_attributes
to know if the locker already exists of if it must create a new one
%>
<% f.fields_for :lockers do |locker_fields| %>
<%= locker_fields.label :name %>
<%= locker_fields.text_field :name %>
<% end %>
<% end %>
and in you controller :
def new
#user = User.new
#user.lockers.build
end
def create
# no need to use build here : params[:user] contains a
# :lockers_attributes key, which has an array of lockers attributes as value ;
# it gets assigned to the user using user.lockers_attributes=,
# a method created by nested_attributes
#user = User.new( params[:user] )
end
as a side note, you can avoid building a new locker for new users in controller in different ways:
create a factory method on User, or override new, or use an after_initialize callback to ensure every new user instantiated gets a locker builded automatically
pass a specific object to fields_for :
<% f.fields_for :lockers, f.object.lockers.new do |new_locker_fields| %>
Someone helped me figure out the solution in a more "Rails 4'y" way with strong attributes & how to override Devise's sign_up_params (to catch all the data coming from my signup form).
def sign_up_params
params.require(:user).permit(:username, :email, :password, :lockers_attributes)
end
Gemfile addition: gem 'strong_parameters'
Commenting out the attr_accessible statement in my user.rb file, since apparently strong parameters eliminate the need for attr_accessible declarations.
# attr_accessible :username, :email, :password, :password_confirmation, :lockers
And the/a correct way of building a Locker before submitting the form: at the beginning of the nested form:
<%= l.input :name, :required => true, label: "Locker name", :placeholder => "Name your first locker" %>
Thanks again for all your help. I know a question like this is difficult to answer without seeing the whole codebase.

Rails Paperclip how to delete attachment?

I am using Paperclip (w/ Amazon s3) on Rails 3. I want to delete an existing attachment without replacing it using an update action.
I've only found one example of this here and could not get that to work, it just wouldn't delete and there was nothing in the logs to say why. I wanted to do something like this on the form:
<%- unless #page.new_record? || !#page.image? -%>
<%= f.check_box :image_delete, :label => 'Delete Image' %>
<%- end -%>
(page is the name of the model, image is the attribute name which holds the attachment)
But how do I detect that checkbox and more importantly, how do I delete the image? I appreciate any help!
First off, when you create a check_box in a form_for (which it looks like you are), then the form should by default send :image_delete as "1" if checked and "0" if unchecked. The method declaration looks like this:
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
Which shows that you can assign other values if you want to, but that is of course optional.
Secondly, the call to manually delete an attachment without deleting the model instance to which it is attached to is:
#page.image.destroy #Will remove the attachment and save the model
#page.image.clear #Will queue the attachment to be deleted
And to accomplish your way of deleting the images through a checkbox, perhaps add something like this to your Page model:
class Page < ActiveRecord::Base
has_attached_file :image
before_save :destroy_image?
def image_delete
#image_delete ||= "0"
end
def image_delete=(value)
#image_delete = value
end
private
def destroy_image?
self.image.clear if #image_delete == "1"
end
end
This way, when you create your form and add the :image_delete checkbox, it will load the default value "0" from the User instance. And if that field is checked then the controller will update the image_delete to "1" and when the User is saved, it will check if the image is to be deleted.
has_attached_file :asset
=>
attr_accessor :delete_asset
before_validation { asset.clear if delete_asset == '1' }
No need to destroy asset, Paperclip will do it.
In the form form.check_box(:delete_asset) will suffice.
This is Benoit's answer, but wrapped in a module, and covering the edge case of nested attribute models where the destroy tickbox is the only thing changed on the model.
It will apply to all attachments on the model.
# This needs to be included after all has_attached_file statements in a class
module DeletableAttachment
extend ActiveSupport::Concern
included do
attachment_definitions.keys.each do |name|
attr_accessor :"delete_#{name}"
before_validation { send(name).clear if send("delete_#{name}") == '1' }
define_method :"delete_#{name}=" do |value|
instance_variable_set :"#delete_#{name}", value
send("#{name}_file_name_will_change!")
end
end
end
end
remember to add this to your Page model too:
attr_accessible :image_delete
Modified version of Paul's solution, to support Rails 5 custom attributes. I just wish there were a way to include the module at the top of the file, before has_attached_file definitions.
module Mixins
module PaperclipRemover
extend ActiveSupport::Concern
included do
attachment_definitions.keys.each do |name|
attribute :"remove_#{name}", :boolean
before_validation do
self.send("#{name}=", nil) if send("remove_#{name}?")
end
end
end
end
end
Was able to achieve this with less code, by just implementing a delete_attachment on the model's side.:
class MyModel < ApplicationRecord
has_attached_file :image
def image_delete=(other)
self.image = nil if other == "1" or other == true
end
end

Update owner tags via form

I would like to uniquely use owner tags in my app. My problem is that when I create / update a post via a form I only have f.text_field :tag_list which only updates the tags for the post but has no owner. If I use f.text_field :all_tags_list it doesn't know the attribute on create / update. I could add in my controller:
User.find(:first).tag( #post, :with => params[:post][:tag_list], :on => :tags )
but then I have duplicate tags, for post and for the owner tags. How can I just work with owner tags?
The answer proposed by customersure (tsdbrown on SO) on https://github.com/mbleigh/acts-as-taggable-on/issues/111 works for me
# In a taggable model:
before_save :set_tag_owner
def set_tag_owner
# Set the owner of some tags based on the current tag_list
set_owner_tag_list_on(account, :tags, self.tag_list)
# Clear the list so we don't get duplicate taggings
self.tag_list = nil
end
# In the view:
<%= f.text_field :tag_list, :value => #obj.all_tags_list %>
I used an observer to solve this. Something like:
in /app/models/tagging_observer.rb
class TaggingObserver < ActiveRecord::Observer
observe ActsAsTaggableOn::Tagging
def before_save(tagging)
tagging.tagger = tagging.taggable.user if (tagging.taggable.respond_to?(:user) and tagging.tagger != tagging.taggable.user)
end
end
Don't forget to declare your observer in application.rb
config.active_record.observers = :tagging_observer
Late to the party, but I found guillaume06's solution worked well, and I added some additional functionality to it:
What this will enable: You will be able to specify the tag owner by the name of the relationship between the tagged model and the tag owner model.
How: write a module and include in your lib on initialization (require 'lib/path/to/tagger'):
module Giga::Tagger
extend ActiveSupport::Concern
included do
def self.tagger owner
before_save :set_tag_owner
def set_tag_owner
self.tag_types.each do |tag|
tag_type = tag.to_s
# Set the owner of some tags based on the current tag_list
set_owner_tag_list_on(owner, :"#{tag_type}", self.send(:"#{tag_type.chop}_list"))
# Clear the list so we don't get duplicate taggings
self.send(:"#{tag_type.chop}_list=",nil)
end
end
end
end
end
Usage Instructions:
Given: A model, Post, that is taggable
A model, User, that is the tag owner
A post is owned by the user through a relationship called :owner
Then add to Post.rb:
include Tagger
acts_as_taggable_on :skills, :interests, :tags
tagger :owner
Make sure Post.rb already has called acts_as_taggable_on, and that User.rb has acts_as_tagger
Note: This supports multiple tag contexts, not just tags (eg skills, interests)..
the set_tag_owner before_save worked for me. But as bcb mentioned, I had to add a condition (tag_list_changed?) to prevent the tags from being deleted on update:
def set_tag_owner
if tag_list_changed?
set_owner_tag_list_on(account, :tags, tag_list)
self.tag_list = nil
end
end
When working with ownership the taggable model gets its tags a little different. Without ownership it can get its tags like so:
#photo.tag_list << 'a tag' # adds a tag to the existing list
#photo.tag_list = 'a tag' # sets 'a tag' to be the tag of the #post
However, both of these opperations create taggins, whose tagger_id and tagger_type are nil.
In order to have these fields set, you have to use this method:
#user.tag(#photo, on: :tags, with: 'a tag')
Suppose you add this line to the create/update actions of your PhotosController:
#user.tag(#photo, on: :tags, with: params[:photo][:tag_list])
This will create two taggings (one with and one without tagger_id/_type), because params[:photo][:tag_list] is already included in photo_params. So in order to avoid that, just do not whitelist :tag_list.
For Rails 3 - remove :tag_list from attr_accessible.
For Rails 4 - remove :tag_list from params.require(:photo).permit(:tag_list).
At the end your create action might look like this:
def create
#photo = Photo.new(photo_params) # at this point #photo will not have any tags, because :tag_list is not whitelisted
current_user.tag(#photo, on: :tags, with: params[:photo][:tag_list])
if #photo.save
redirect_to #photo
else
render :new
end
end
Also note that when tagging objects this way you cannot use the usual tag_list method to retrieve the tags of a photo, because it searches for taggings, where tagger_id IS NULL. You have to use instead
#photo.tags_from(#user)
In case your taggable object belongs_to a single user you can also user all_tags_list.
Try using delegation:
class User < ActiveRecord::Base
acts_as_taggable_on
end
class Post < ActiveRecord::Base
delegate :tag_list, :tag_list=, :to => :user
end
So when you save your posts it sets the tag on the user object directly.
I ended up creating a virtual attribute that runs the User.tag statement:
In my thing.rb Model:
attr_accessible :tags
belongs_to :user
acts_as_taggable
def tags
self.all_tags_list
end
def tags=(tags)
user = User.find(self.user_id)
user.tag(self, :with => tags, :on => :tags, :skip_save => true)
end
The only thing you have to do is then change your views and controllers to update the tag_list to tags and make sure you set the user_id of the thing before the tags of the thing.

How do I handle nils in views?

I have the following models set up:
class Contact < ActiveRecord::Base
belongs_to :band
belongs_to :mode
validates_presence_of :call, :mode
validates_associated :mode, :band
validates_presence_of :band, :if => :no_freq?
validates_presence_of :freq, :if => :no_band?
protected
def no_freq?
freq.nil?
end
def no_band?
band.nil?
end
end
class Band < ActiveRecord::Base
has_many :logs
end
class Mode < ActiveRecord::Base
has_many :logs
end
When I enter a frequency on my new view it allows for no band to be specified if a freq is entered. This creates a problem in my other views though because band is now nil. How do I allow for band not to be specified and just show up as empty on my index and show views, and then in the edit view allow one to be specified at a later point in time.
I have been able to get my index to display a blank by doing:
contact.band && contact.band.name
But I'm not sure if this is a best approach, and I'm unsure of how to apply a similar solution to my other views.
Many thanks from a rails newb!
In my views, I use the following for potentially nil objects in my views:
<%= #contact.band.name unless #contact.band.blank? %>
if your object is an array or hash, you can use the empty? function instead.
<%= unless #contacts.empty? %>
..some code
<% end %>
Hope this helps!
D
A couple years old but still a top Google result for "rails view handle nil" so I'll add my suggestion for use with Rails 3.2.3 and Ruby 1.9.3p0.
In application_helper.rb, add this:
def blank_to_nbsp(value)
value.blank? ? " ".html_safe : value
end
Then to display a value in a view, write something like this:
<%= blank_to_nbsp contact.band %>
Benefits:
"blank" catches both nil values and empty strings (details).
Simply omitting a nil object, or using an empty string, may cause formatting issues. pushes a non-breaking space into the web page and preserves formatting.
With the "if" and "unless" suggestions in other answers, you have to type each object name twice. By using a helper, you only have to type each object name once.
<%= #contact.try(:band).try(:name) %>
This will return nil if band or name do not exist as methods on their respective objects.
You can use Object#andand for this:
<%= #contact.band.andand.name %>
<%= #contact.band if #contact.band %> also works

Resources