I'm reworking a Rails 2 website. Right now, I'm getting an error, and I think it's because a value is being submitted as blank instead of .nil. However, my attempts to keep it nil don't seem to be working. I appreciate any help you have to offer.
From Model, based on Make blank params[] nil
NULL_ATTRS = %w( start_masterlocation_id )
before_save :nil_if_blank
protected
def nil_if_blank
NULL_ATTRS.each { |attr| self[attr] = nil if self[attr].blank? }
end
View
I've got jQuery that adds a value to this hidden field when a start_masterlocation_id exists. I've also got jQuery that removes the field attribute when it does not exist.
<%= hidden_field :newsavedmap, :start_masterlocation_id, :id => "start-masterlocation-id-field" %>
Finally, here is the part of the controller that is throwing the error. This is the controller for the page that holds the form (Maptry), not the controller for Newsavedmap. I think I have to delete the #newsavedmap.id, #newsavedmap.mapname, and #newsavedmap.optimize lines now that I'm going with form handlers, but I don't think that's related to this error.
Controller
def maptry
#itinerary = Itinerary.find(params[:id])
if #itinerary.user_id == current_user.id
respond_to do |format|
#masterlocation = Masterlocation.find(:all)
format.html do
end
format.xml { render :xml => #masterlocation }
format.js do
#masterlocation = Masterlocation.find(:all)
render :json => #masterlocation.to_json(:only => [:id, :nickname, :latitude, :longitude])
end
end
if params[:newsavedmap_id]
#newsavedmap = Newsavedmap.find(:first, :conditions => {:id => params[:newsavedmap_id]})
#waypoint = Waypoint.find(:all, :conditions => {:newsavedmap_id => params[:newsavedmap_id]})
#newsavedmap.id = params[:newsavedmap_id]
#newsavedmap.mapname = Newsavedmap.find(:first, :conditions => {:id => params[:newsavedmap_id]}).mapname
#newsavedmap.optimize = Newsavedmap.find(:first, :conditions => {:id => params[:newsavedmap_id]}).optimize
if !#newsavedmap.start_masterlocation_id.nil?
#start_name = Masterlocation.find(:first, :conditions => {:id => #newsavedmap.start_masterlocation_id}).name
end
if !#newsavedmap.end_masterlocation_id.nil?
#end_name = Masterlocation.find(:first, :conditions => {:id => #newsavedmap.end_masterlocation_id}).name
end
else
#newsavedmap = Newsavedmap.new
end
else
redirect_to '/'
end
end
Error
This does not occur when a start_masterlocation_id is present in the database.
undefined method `name' for nil:NilClass
I solved this by ignoring the nil problem. Instead, I used
!#newsavedmap.start_masterlocation_id.blank?
This evaluates whether the data in the record is blank and then either does or doesn't do something. Obviously, this results in unwanted "blank" data being left inside my database, so it's not ideal, but it helped me move forward with the project. I'd appreciate any responses that deal directly with the nil issue and tell me how to have Rails ignore blank data in a form.
Related
I'm hoping this is a simple question - I have the following helper code:
module ApplicationHelper
def add_feature_fields(feature_types, object_form_builder, actions_visible)
feature_types.length.times {object_form_builder.object.features.build}
i = 0
fields = object_form_builder.fields_for :features do |features_builder|
render :partial => "features/fixed_feature", :locals => {:feature => features_builder, :fixed_feature_type => feature_types[i], :form_actions_visible => actions_visible}
i = i + 1
end
end
end
The code is working as expected, except for the line i = i + 1. For some reason, this seems to be breaking the loop, and nothing is rendered. Evidently, I am doing this wrong somehow - perhaps fields_for is not a normal loop?
How can I increment i by 1 each time the loop runs?
I'm not sure about the below code, but something around this should work and fix the issue. Give a try
object_form_builder.each.with_index do |builder,index|
object_form_builder.fields_for :features, builder do |feature_builder|
render :partial => "features/fixed_feature", :locals => {:feature => features_builder, :fixed_feature_type => feature_types[i], :form_actions_visible => actions_visible}
end
end
I was able to get this working by doing the following:
module ApplicationHelper
def add_feature_fields(feature_types, object_form_builder, actions_visible)
feature_types.length.times {object_form_builder.object.features.build}
i = -1
object_form_builder.fields_for :features do |features_builder|
i = i + 1
render :partial => "features/fixed_feature", :locals => {:feature => features_builder, :fixed_feature_type => feature_types[i], :form_actions_visible => actions_visible}
end
end
end
I believe what was happening was that when I did i = i + 1 after I called render, the return value was the iterator and not render (since the method returns the last value).
posts_controller.rb destroy method
def destroy
if !request.xhr?
render_404
return
end
if user_signed_in?
if Post.exists?(:post_id => params[:id])
if Post.post_is_mine(params[:id], current_user.id)
#return = { :error => false, :response => "Post deleted" }
else
#return = { :error => true, :response => 'You are not allowed to perform this action.' }
end
else
#return = { :error => true, :response => 'This post doesn\'t exist.' }
end
else
#return = { :error => true, :response => 'Please login before you delete a post.' }
end
render :json => ActiveSupport::JSON.encode( #return )
end
post.rb
def self.post_is_mine(post_id, user_id)
#where(:user_id => user_id, :post_id => bucket_id)
where("user_id = ? and post_id = ?", user_id, bucket_id)
end
when i check what queries are being run when i destroy a post, i can only see the .exists? to run but not the .post_is_mine which simply goes through as it returns TRUE
i tried several other names as the method since something could cause the problem or even simply trying out the if statement with .post_is_mine but still, the query was not run
Could there be a problem on the model as to how i use the where clause?
Yes. #where returns an ActiveRecord Relation, which is used to generate your query. The relation will not be evaluated in your code, so the query from .post_is_mine will never be executed. if Post.postis mine(params[:id], current_user.id) returns true because the Relation object is not nil.
What you really want is to use exists? in the post_is_mine method.
def self.post_is_mine(post_id, user_id)
exists?(:user_id => user_id, :post_id => bucket_id)
end
EDIT:
I was curious about the difference between my answer and Pavling's answer. For anyone else wondering:
#exists? executes a SQL statement with SELECT 1 FROM ...
#any? executes a SQL statement with SELECT COUNT(*) FROM ...
In practice there is likely not much difference between the two, but some crude benchmarks indicated that #any? is faster (with AR 3.2.6 and Postgresql 9.1 on OSX)
The "where" will return an empty collection, which evaluates as truthy. You would need to add a check to see if there are any records in it to get the right true/false.
def self.post_is_mine(post_id, user_id)
where("user_id = ? and post_id = ?", user_id, bucket_id).any?
end
Is there a way to run only validations of a specific type?
I have an application that updates multiple class instances in a single form. Validations are performed by creating an instance of building and validating on that.
The problem: if an attribute isn't being updated, the form field is left blank and the form submits an empty string. You can see an example here where params[:building][:name] is an empty string.
params = {:building=>{:name=>"", :short_name=>"", :code=>"test"}, :commit=>"Update Buildings", :building_ids=>["2", "5", "7"], :action=>"update_multiple", :controller=>"buildings"}
How can I run all the validations excluding those that check for the presence of an attribute?
def update_multiple
#building = Building.new(params[:building].reject {|k,v| v.blank?})
respond_to do |format|
if #building.valid?
Building.update_all( params[:building].reject {|k,v| v.blank?}, {:id => params[:building_ids]} )
format.html { redirect_to buildings_path, notice: 'Buildings successfully updated.' }
else
#buildings = Building.all
format.html { render action: 'edit_multiple' }
end
end
end
I've spent quite a bit of time working on this, and here's what I've found so far:
To retrieve a models validations
$ Building.validators
=> [#<ActiveModel::Validations::PresenceValidator:0x007fbdf4d6f0b0 #attributes=[:name], #options={}>]
To get a validators kind
$ Building.validators[0].kind
=> :presence
This is the method used by rails to run validations:
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/callbacks.rb
line 353
# This method runs callback chain for the given kind.
# If this called first time it creates a new callback method for the kind.
# This generated method plays caching role.
#
def __run_callbacks(kind, object, &blk) #:nodoc:
name = __callback_runner_name(kind)
unless object.respond_to?(name, true)
str = object.send("_#{kind}_callbacks").compile
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{name}() #{str} end
protected :#{name}
RUBY_EVAL
end
object.send(name, &blk)
end
If there is a way to run validations directly? If so, I could iterate over the Building.validators and only run those with kind != :presence.
I'd love to hear any ideas you have.
I think circumventing specific validations is an interesting idea, but I think there's an easier way. I'd handle this by writing a custom method that validates batch updates, something like this:
def valid_for_batch?(params)
#building = Building.new(params[:building].reject {|k,v| v.blank?})
#building.name = "Foo" if #building.name.blank?
#building.shortname = "Bar" if #building.shortname.blank?
# etc...
#building.valid?
end
Just make sure that "Foo" and "Bar" up there are values that will pass your validation - all that code is doing is looking to see if the values are empty and if so, replacing them with a temporary value that will pass validation. That way, the only way that #building.valid? will return false at the end is if there were existing bad data.
Wow, after working on this issue for many hours, it looks to be a very difficult problem.
Create a class instance #building and set placeholders for attributes whose presence is validated.
I've tried many different approaches and this is the best I can come up with so far.
def update_multiple
valid = true
#building = Building.new(params[:building])
set_bulk_edit_placeholders_for_presence_validators(#building)
building_valid = #building.valid?
# Check if buildings were selected to edit
if params[:building_ids].blank?
valid = false
#building.errors.add(:base, 'You must select at least one Building')
end
# Check if all form fields are blank
if params[:building].values.delete_if {|v| v.blank?}.blank?
valid = false
#building.errors.add(:base, 'The edit form must not be empty')
end
respond_to do |format|
if valid && building_valid
#buildings = Building.find(params[:building_ids])
#buildings.each do |building|
building.update_attributes!(params[:building].reject { |k,v| v.blank? })
end
format.html { redirect_to buildings_path, notice: 'Buildings were successfully updated.' }
else
#buildings = Building.all
format.html { render edit_multiple_buildings_path }
end
end
end
This is a general function to set the placeholders. It can be used for any model from any controller.
application_controller.rb
private
def set_bulk_edit_placeholders_for_presence_validators(class_instance, hash={})
model_name = hash[:model_name] || self.class.name.sub("Controller", "").singularize.downcase
klass = self.class.name.sub("Controller", "").singularize.constantize
klass.send(:validators).each { |validator|
if (validator.kind == :presence)
validator.attributes.each { |attribute|
if params[model_name][attribute].blank?
class_instance.send("#{attribute}=", 'placeholder')
end
}
end
}
end
In order to display the errors properly on the form it has to be a form_for #buildings.
Since the placeholders are set for the #buildings object, we have to specify that the form values come directly from params.
edit_multiple.haml
= bootstrap_form_for #building, :url => update_multiple_buildings_path, :method => :put, :html => {:class => 'form-horizontal'} do |f|
= f.text_field :name, :value => params[:building].try(:send, :[], :name)
= f.text_field :short_name, :value => params[:building].try(:send, :[], :short_name)
= f.text_field :code, :value => params[:building].try(:send, :[], :code)
= f.submit 'Update Buildings', :name => nil
%table.table.table-striped.table-bordered
%thead
%tr
%th.check_box_column= check_box_tag 'select_all'
%th.sortable= sortable :name
%th Abbr Name
%th.sortable= sortable :code
%th
%tbody
- #buildings.each do |building|
%tr
%td.check_box_column= check_box_tag 'building_ids[]', building.id, (params[:building_ids].include?(building.id.to_s) if params[:building_ids])
%td= building.name
%td= building.short_name
%td= building.code
%td.links.span2
= link_to 'Show', building
= link_to 'Edit', edit_building_path(building)
= link_to 'Delete', building, :method => :delete, :confirm => 'Are you sure you want to delete this building?'
I hope this can be of help to others working on similar "bulk update" validation issues.
I am trying to create a unique json data structure, and I have run into a problem that I can't seem to figure out.
In my controller, I am doing:
favorite_ids = Favorites.all.map(&:photo_id)
data = { :albums => PhotoAlbum.all.to_json,
:photos => Photo.all.to_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
render :json => data
and in my model:
def as_json(options = {})
{ :name => self.name,
:favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : options[:favorite] }
end
The problem is, rails encodes the values of 'photos' & 'albums' (in my data hash) as JSON twice, and this breaks everything... The only way I could get this to work is if I call 'as_json' instead of 'to_json':
data = { :albums => PhotoAlbum.all.as_json,
:photos => Photo.all.as_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
However, when I do this, my :favorite => lambda option no longer makes it into the model's as_json method.......... So, I either need a way to tell 'render :json' not to encode the values of the hash so I can use 'to_json' on the values myself, or I need a way to get the parameters passed into 'as_json' to actually show up there.......
I hope someone here can help... Thanks!
Ok I gave up... I solved this problem by adding my own array methods to handle performing the operations on collections.
class Array
def to_json_objects(*args)
self.map do |item|
item.respond_to?(:to_json_object) ? item.to_json_object(*args) : item
end
end
end
class Asset < ActiveRecord::Base
def to_json_object(options = {})
{:id => self.id,
:name => self.name,
:is_favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : !!options[:favorite] }
end
end
class AssetsController < ApplicationController
def index
#favorite_ids = current_user.favorites.map(&:asset_id)
render :json => {:videos => Videos.all.to_json_objects(:favorite => lambda {|v| #favorite_ids.include?(v.id)}),
:photos => Photo.all.to_json_objects(:favorite => lambda {|p| #favorite_ids.include?(p.id)}) }
end
end
I think running this line of code
render :json => {:key => "value"}
is equal to
render :text => {:key => "value"}.to_json
In other words, don't use both to_json and :json.
I want to display different types of objects in the same ajax called controller function. I want to render out when I send back an ajax call. The problem is I want the "title" attribute of one object and the "name" attribute of another object. I can't render 2 partials and can seem to figure out how to check the type of object an object is. How do I solve this problem?
Here is my current controller setup (NOT CORRECT)
#objects = Obj.find(:all,:conditions => ["name Like ?", "#{prefix}%"])
#moreObjects = ObjTwo.find(:all,:conditions => ["title Like ?","#{prefix}%"])
if #objects.empty?
render :text => "No Matches"
else
render :partial => 'one', :collection => #objects
end
if #moreObjects.empty?
render :text => "No Matches"
else
render :partial => 'two', :collection => #moreObjects
end
try something like
<%= obj.title if obj.respond_to?(:title) %>
Here's one option that doesn't involve checking its type - in your Obj and ObjTwo classes add a new method:
def fancy_pants
title
end
def fancy_pants
name
end
Then you can combine #objects and #moreObjects - you'll only need one if statement and one partial.
#search_domination = #objects + #moreObjects
if #search_domination.empty?
render :text => "No Matches"
else
render :partial => 'one', :collection => #search_domination
end
You could use Object.is_a? but that's not a very clean solution. You could also try mapping your data before presentation to help keep your views lightweight.