How to get the maximum length configured in an ActiveRecord validation?

被刻印的时光 ゝ 提交于 2019-12-18 12:12:44

问题


Given a model:

class Person
  validates_lenght_of :name, :maximum => 50
end

I have some view code that shows a countdown and enforces this maximum. However I hard coded the number 50 into that view code. Is there a way to extract this number from the model?

Something like:

Person.maximum_length_of_name

I tried this:

Person.validators_on(:name)
 => [#<ActiveRecord::Validations::UniquenessValidator:0x000001067a9840 @attributes=[:name], @options={:case_sensitive=>true}, @klass=Trigger(id: integer, name: string, created_at: datetime, updated_at: datetime, user_id: integer, slug: string, last_update_by: integer)>, #<ActiveModel::Validations::PresenceValidator:0x000001067a6c30 @attributes=[:name], @options={}>, #<ActiveModel::Validations::LengthValidator:0x000001067a3f08 @attributes=[:name], @options={:tokenizer=>#<Proc:0x00000101343f08@/Users/sjors/.rvm/gems/ruby-1.9.2-p0/gems/activemodel-3.0.6/lib/active_model/validations/length.rb:9 (lambda)>, :maximum=>50}>]

The information is in there, but I don't know how to extract it:


回答1:


Use validators_on method

irb(main):028:0> p Person.validators_on(:name)[0].options[:maximum]
50
=> 50

As @Max Williams mentioned it works only on Rails 3




回答2:


The problem with @nash answer is that validators do not own a certain order. I figured out how to do the same thing with just some more code but in some kind of safer mode ('cause you can add more validators later and break the order you get it):

(Person.validators_on(:name).select { |v| v.class == ActiveModel::Validations::LengthValidator }).first.options[:maximum]

I think it does only work for Rails 3 too.




回答3:


[Edit 2017-01-17] Carefull my answer is old (2012) and was for Rails 3. It may not work / be ideal for newer Rails versions.


Just to bring a little more DRY spirit, you could create a generic class method to get maximum "length_validator" value on any attribute, like so:

Create a module in your lib directory and make it extend ActiveSupport::Concern:

module ActiveRecord::CustomMethods
  extend ActiveSupport::Concern
end

# include the extension 
ActiveRecord::Base.send(:include, ActiveRecord::CustomMethods)

Add the "module ClassMethods" in it and create the "get_maximum" method:

module ActiveRecord::CustomMethods
  extend ActiveSupport::Concern

  module ClassMethods
    def get_maximum(attribute)
      validators_on(attribute).select{|v| v.class == ActiveModel::Validations::LengthValidator}.first.options[:maximum]
    end
  end
end

EDIT 1: Configuration

You'll also have to add a require in one of your initializers.

For instance, here are my configurations:

  1. I've put my file in lib/modules/active_record/extensions.
  2. I've added this in my autoload_paths: config.autoload_paths += %W(#{config.root}/lib/modules) Note: this is not required, but best practice if you want to put there some of your custom classes and modules that you share between your apps.
  3. In one of my initializers (config/initializers/custom.rb) I've added this line: require "active_record/extensions"

And that should do it! Restart your server and then...

END EDIT 1

And then you should be able to do something like this:

<%= form_for @comment do |f| %>
  <%= f.text_area(:body, :maxlength => f.object.class.get_maximum(:body)) #Or just use Comment.get_maximum(:body) %>
<% end %>

I hope it will help others! :) Of course you can customize the method the way you want and add options and do fancy stuff. ;-)




回答4:


More concise:

Person.validators_on(:name).detect { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }.options[:maximum]

Uses detect{} instead of select{}.first and is_a? instead of class ==.
That works with Rails 4.1 as well.




回答5:


Even more dynamic than Kulgar's answer would be to use something like this:

Person.validators_on(attribute)
  .select { |v| v.class == ActiveRecord::Validations::LengthValidator }
  .select { |v| v.options[:maximum].present? }.first.options[:maximum]

That way you can order the validators inside the model the way you want.

Use it as a Rails helper

You then could use this code to write a helper:

# Returns the maximum length for a given attribute of a model
def get_maxlength(model, attribute)
  model.validators_on(attribute)
    .select { |v| v.class == ActiveRecord::Validations::LengthValidator }
    .select { |v| v.options[:maximum].present? }.first.options[:maximum]
end

And utilize the helper like this:

= f.text_field :name, maxlength: get_maxlength(f.object.class, :name) # Or use get_maxlength(Person, :name)



回答6:


This is an indirect answer, but is an alternative solution, just in case it may prove useful to anyone.

Alternative Solution 1

class Person
  MAX_LENGTHS = {
    name: 50,
    # email: 30, etc...
  }.freeze

  validates :name, length: { maximum: MAX_LENGTHS.fetch(:name) }
end

# usage example in view file
<%= Person.MAX_LENGTHS.fetch(:name) %>

Alternative Solution 2

... or if you prefer one-liners, or to not use a Hash constant

class Person
  validates :name, length: { maximum: (MAX_NAME_LENGTH = 50) }
  # validates :password, length: {
  #   minimum: (MIN_PASSWORD_LENGTH = 8),
  #   maximum: (MAX_PASSWORD_LENGTH = 70)
  # }
  # ... etc
end

# usage example in view file
<%= Person.MAX_NAME_LENGTH %>



回答7:


If you're in rails 2, it's not easy. I remember trying this before and not getting far. You can get at the validation objects but they don't list which field they are for which seemed very odd to me. People have done plugins for this (ie to inspect or reflect on an AR object's validations), such as https://github.com/redinger/validation_reflection




回答8:


Kulgar's answer is good and possibly ideal. Here is an easier way to do this for Rails 4 that does not require modifying any configuration, with the disadvantage that you have to add an include line to every model that wants to use it.

models/concerns/introspection.rb:

module Introspection

  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def max_length(property)
      Resource.validators_on(property.to_sym).
          select{ |v| v.kind_of?(ActiveModel::Validations::LengthValidator) }.
          first.options[:maximum]
    end
  end

end

Then in your model:

class Comment < ActiveRecord::Base
  include Introspection
  ..
end

Then you can do something like this:

<%= form_for @comment do |f| %>
  <%= f.text_area(:body, :maxlength => f.object.class.max_length(:body)) %> # Or just use Comment.max_length(:body) %>
<% end %>


来源:https://stackoverflow.com/questions/6015001/how-to-get-the-maximum-length-configured-in-an-activerecord-validation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!