barsoom/traco

failed validates uniquesness

chamnap opened this issue · 6 comments

I have a simple Tag model which have two languages: name_en and name_es. Everything works fines except the uniqueness validation.

class Tag < ActiveRecord::Base
  translates :name

  validates :name, :presence => true, :uniqueness => true
end

Here is the full backtrace:

undefined method `text?' for nil:NilClass
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/validations/uniqueness.rb:57:in `build_relation'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/validations/uniqueness.rb:25:in `validate_each'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activemodel-3.2.8/lib/active_model/validator.rb:153:in `block in validate'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activemodel-3.2.8/lib/active_model/validator.rb:150:in `each'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activemodel-3.2.8/lib/active_model/validator.rb:150:in `validate'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/callbacks.rb:310:in `_callback_before_115'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/callbacks.rb:418:in `_run__4486747074408038687__validate__3157551461272750466__callbacks'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/callbacks.rb:405:in `__run_callback'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/callbacks.rb:385:in `_run_validate_callbacks'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/callbacks.rb:81:in `run_callbacks'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activemodel-3.2.8/lib/active_model/validations.rb:227:in `run_validations!'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activemodel-3.2.8/lib/active_model/validations/callbacks.rb:53:in `block in run_validations!'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/callbacks.rb:403:in `_run__4486747074408038687__validation__3157551461272750466__callbacks'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/callbacks.rb:405:in `__run_callback'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/callbacks.rb:385:in `_run_validation_callbacks'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/callbacks.rb:81:in `run_callbacks'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activemodel-3.2.8/lib/active_model/validations/callbacks.rb:53:in `run_validations!'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activemodel-3.2.8/lib/active_model/validations.rb:194:in `valid?'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/validations.rb:69:in `valid?'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/validations.rb:77:in `perform_validations'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/validations.rb:56:in `save!'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/attribute_methods/dirty.rb:33:in `save!'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/transactions.rb:246:in `block in save!'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/transactions.rb:295:in `block in with_transaction_returning_status'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/connection_adapters/abstract/database_statements.rb:192:in `transaction'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/transactions.rb:208:in `transaction'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/transactions.rb:293:in `with_transaction_returning_status'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/transactions.rb:246:in `save!'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/validations.rb:41:in `create!'
/home/ubuntu/workspace/od/tpmnk/db/seeds.rb:27:in `block in <top (required)>'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/spreadsheet-0.7.3/lib/spreadsheet/worksheet.rb:133:in `block in each'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/spreadsheet-0.7.3/lib/spreadsheet/worksheet.rb:132:in `upto'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/spreadsheet-0.7.3/lib/spreadsheet/worksheet.rb:132:in `each'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/spreadsheet-0.7.3/lib/spreadsheet/excel/worksheet.rb:35:in `each'
/home/ubuntu/workspace/od/tpmnk/db/seeds.rb:26:in `<top (required)>'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/dependencies.rb:245:in `load'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/dependencies.rb:245:in `block in load'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/dependencies.rb:236:in `load_dependency'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activesupport-3.2.8/lib/active_support/dependencies.rb:245:in `load'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/railties-3.2.8/lib/rails/engine.rb:520:in `load_seed'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@tpmnk/gems/activerecord-3.2.8/lib/active_record/railties/databases.rake:309:in `block (2 levels) in <top (required)>'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/task.rb:205:in `call'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/task.rb:205:in `block in execute'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/task.rb:200:in `each'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/task.rb:200:in `execute'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/task.rb:158:in `block in invoke_with_call_chain'
/home/ubuntu/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/monitor.rb:211:in `mon_synchronize'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/task.rb:151:in `invoke_with_call_chain'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/task.rb:144:in `invoke'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/application.rb:116:in `invoke_task'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/application.rb:94:in `block (2 levels) in top_level'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/application.rb:94:in `each'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/application.rb:94:in `block in top_level'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/application.rb:133:in `standard_exception_handling'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/application.rb:88:in `top_level'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/application.rb:66:in `block in run'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/application.rb:133:in `standard_exception_handling'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/lib/rake/application.rb:63:in `run'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/gems/rake-0.9.2.2/bin/rake:33:in `<top (required)>'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/bin/rake:19:in `load'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/bin/rake:19:in `<main>'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/bin/ruby_noexec_wrapper:14:in `eval'
/home/ubuntu/.rvm/gems/ruby-1.9.3-p194@global/bin/ruby_noexec_wrapper:14:in `<main>'
Tasks: TOP => db:seed

The reason that causes this error, it's because the uniqueness validation tries to select from the db while I don't have that column. What is the best way to solve this problem?

I would probably just validate on the actual columns:

validates :name_es, :name_en, :uniqueness => true

I don't know off the top of my head how complicated it would be to support the regular uniqueness validation in Traco, but I'm guessing it would be fairly tricky.

I'm fine with just validating on the columns themselves per above. That means you can always require the English translation and never require the Spanish translation, for example, no matter what the current locale is.

I'll close this ticket for that reason, but feel free to comment again with reasons to support it, suggestions on how etc.

yeah, there is no better way than that. I think I need to write some custom validations. Thanks for your feedback. :)

Experienced the same "issue". More or less the same applies to other methods, e.g. setting attr_accessible. I solved/circumvented it by adding something like this to the model:

I18n.available_locales.each do |l|
  l_attr = "name_#{ l }"
  attr_accessible l_attr
  validates_uniqueness_of l_attr
  # etc... whatever validations need to run on translatable attributes
end

So I'm simply looping over the available locales. Would it be an idea to add something like this to Traco? It would need to be more generic of course, but what do you think about adding some code that checks for attr_accessible and validations on translatable attributes and then applies this to the translation attributes? There are some more details to be figured out, like taking default locale into account, etc.

@henrik Would you consider accepting a patch implementing this?

@corneverbruggen You could do locales_for_column(:name).each do |l| … in case that ever differs from available_locales.

About adding a feature – I'd love for this to be easier, as long as it doesn't add a lot of complexity or is over-specific to
some uncommon case.

What kind of method/methods would you suggest?

Maybe all that's needed is a method for "this attribute in all locales", something like:

attr_accessible *locale_columns(:name)
validates *locale_columns(:name), :uniqueness => true

or even

<% Post.locale_columns(:title).each do |column| %>
  <p>
    <%= form.label column %>
    <%= form.text_field column %>
  </p>
<% end %>

Not very happy with the name. Traco already has .translatable_columns (returns e.g. [:title, :body]) and e.g. .locales_for_column(:title) (returns something like [:sv, :en].

"Columns" seem to generally be the DB fields and "attributes" any accessor, backed by DB or not. So maybe translatable_attributes, locales_for_attribute and locale_columns would be a little better.

Alright, I went ahead and added locale_columns and renamed translatable_columns -> translatable_attributes and locales_for_column -> locales_for_attribute. I'll stick with the name "Traco" even if "Tratt" seems a better name in retrospect ;)

Feel free to comment again it if you think more should be done.