How do I get translated error messages in sequel? - ruby

I am using Sequel. How do I get ActiveModel-style translated errors?
Example
class User < Sequel::Model
def validate
super
errors.add(:email, :invalid)
end
end
#user = User.new
#user.save # => false
#user.errors.full_messages # => ["email invalid"]
I want it to return a translated error using the config/locales data. When I18n.locale = :en it should return ["Email is invalid"], when I18n.locale = :de it should return ["Derrrrr E-Reichspost ist ungültig"] (and so on). How can I get translated error messages in sequel?

You need to modify the validation_helpers plugin DEFAULT_OPTIONS. Here's an example: http://pastie.org/4251873

Related

Ruby on rails:undefined method `id' for nil:NilClass

i'm trying to learn Ruby on rails through this tutorial:http://net.tutsplus.com/tutorials/other/building-a-forum-from-scratch-with-ruby-on-rails/
I'm on step 8 but got an error:undefined method `id' for nil:NilClass. this error is around
current_user.id. i'm using rails 4 and i I think the turoriels is for an older version , may be this is why i got this error. Can someone help me? sorry for my english.
this is the portion of code in my controller where the issue is.
#topic = Topic.new(params[:topic])
if #topic.save..
#topic = Topic.new(:name => params[:topic][:name], :last_poster_id => current_user.id, :last_post_at => Time.now, :forum_id => params[:topic][:forum_id])
if #post.save..
flash[:notice] = "topic créé avec succès."
redirect_to "/forums/#{#topic.forum_id}"
The only possible place in your code as shown that could produce that warning is current_user.id. current_user must be nil.
The problem is that you didn't set the session[:user_id].
Try this to set it with something like:
MyApp::MyApp.helpers do
def current_user=(user)
session[:user_id] = user ? user.id : nil
end
def current_user do
User.find(session[:user_id]) rescue false
end
end
MyApp.controllers :session do
# to create a new session
#
# #param email [String]
# #param password [String]
post :login do
user = User.find(email: params[:email])
if user.password == params[:password].encrypt!
current_user = user
else
error 401, 'Wrong email/password.'
end
end
end

Ruby Sinatra + Sequel constraint error handling

What is the right way to handle exceptions coming from the model in Sequel? Particularly the thing I'm running into is when the unique constraint is applied to the login. The exception in this case appears to be coming from SQLite itself instead of Sequel which means it's not getting handled by "errors".
This is the error I'm getting after trying to create a user with a "non-unique" login:
Sequel::DatabaseError at /user/create
SQLite3::ConstraintException: column login is not unique
file: database.rb location: close line: 97
Here is my abbreviated code:
require 'sinatra'
require 'sequel'
DB.create_table :users do
primary_key :id
String :login, :key => true, :length => (3..40), :required => true, :unique => true
String :hashed_password, :required => true
String :salt
DateTime :created_at, :default => DateTime.now
end
class User < Sequel::Model
# password authentication code
end
get '/user/create' do
slim :user_create
end
post '/user/create' do
user = User.new
user.login = params['login']
user.password = params['password']
if user.save
'User created'
else
tmp = []
user.errors.each do |e|
tmp << (e.join('<br/>'))
end
tmp
end
end
You probably want to use the validation_helpers plugin and use validates_unique :login inside the validate method.
Add the code below to handle sequel errors in sinatra.
The error block in sinatra will handle any errors thrown in one of your routes. You can specify the type of error in the first line in order to only handle those types of errors.
error Sequel::Error do
e = env['sinatra.error']
content_type :json
status(400)
return {
message: e.message
}.to_json
end
If you wanted to handle all errors, you would insert the following block of code.
error Exception do
e = env['sinatra.error']
content_type :json
status(400)
return {
message: e.message
}.to_json
end
You can combine many of these blocks so that you are handling different types of errors differently.
First - use validation plugin
# models/account.rb
Sequel::Model.plugin :validation_helpers
class Account < Sequel::Model
def validate
super
validates_unique :login
end
end
Next - handle error in app
# app.rb
...
begin
Account.create
rescue => e
# do what you need
end
...

Generating JSON for Sinatra

I'm having an issue with passing the generated JSON notation of my object to my Sinatra application. The problem I have is twofold:
I have 2 classes that are mapped to a database using the Sequel gem. When they generate JSON it is ok and properly implemented.
I have a custom class called registration that maps one of the classes with an additional field. The goal is to generate JSON out of this and pass that JSON to the application using cucumber (test purpose)
The application code responsible for handling the request has the following function defined:
post '/users' do
begin
hash = JSON.parse(self.request.body.read)
registration = Registration.new.from_json(#request.body.read)
registration.user.country = Database::Alaplaya.get_country_by_iso_code(registration.user.country.iso_code)
return 400 unless(registration.is_valid?)
id = Database::Alaplaya.create_user(registration.user)
# If the registration failed in our system, return a page 400.
return 400 if id < 1
end
problem 1: I cannot use the params hash. It exists but is just an empty hash. Why?
problem 2: I cannot deserialize the JSON generated by the class itself. Why?
The registration class looks like this:
require 'json'
class Registration
attr_accessor :user, :project_id
def to_json(*a)
{
'json_class' => self.class.name,
'data' => [#user.to_json(*a), #project_id]
}.to_json(*a)
end
def self.json_create(o)
new(*o['data'])
end
# Creates a new instance of the class using the information provided in the
# hash. If a field is missing in the hash, nil will be assigned to that field
# instead.
def initialize(params = {})
#user = params[:user]
#project_id = params[:project_id]
end
# Returns a string representing the entire Registration.
def inspect
"#{#user.inspect} - #{#user.country.inspect} - #{#project_id}"
end
# Returns a boolean valid representing whether the Registration instance is
# considered valid for the API or not. True if the instance is considered
# valid; otherwise false.
def is_valid?
return false if #user.nil? || #project_id.nil?
return false if !#user.is_a?(User) || !#project_id.is_a?(Fixnum)
return false if !#user.is_valid?
true
end
end
I had to implement the methods to generate the JSON output correctly. When I run this in console I get the following output generated:
irb(main):004:0> r = Registration.new(:user => u, :project_id => 1)
=> new_login - nil - 1
irb(main):005:0> r.to_json
=> "{\"json_class\":\"Registration\",\"data\":[\"{\\\"json_class\\\":\\\"User\\\
",\\\"login\\\":\\\"new_login\\\"}\",1]}"
Which looks like valid JSON to me. However when I POST this to the application server and try to parse this, JSON complains that at least 2 octets are needed and refuses to deserialize the object.
If you're using Sequel as your ORM, try something like this:
In your model:
class Registration < Sequel::Model
many_to_one :user
many_to_one :project
plugin :json_serializer
end
The server:
before do
#data = JSON.parse(request.body.read) rescue {}
end
post '/users' do
#registration = Registration.new #data
if #registration.valid?
#registration.save
#registration.to_json #return a JSON representation of the resource
else
status 422 #proper status code for invalid input
#registration.errors.to_json
end
end
I think you may be overcomplicating your registration process. If the HTTP action is POST /users then why not create a user? Seems like creating a registration is overly complex. Unless your user already exists, in which case POST /users would be incorrect. If what you're really intending to do is add a user to to a project, then you should PUT /projects/:project_id/users/:user_id and the action would look something like this:
class User < Sequel::Model
many_to_many :projects
end
class Project < Sequel::Model
many_to_many :users
end
#make sure your db schema has a table called users_projects or projects_users
put '/projects/:project_id/users/:user_id' do
#find the project
#project = Project.find params[:project_id]
raise Sinatra::NotFound unless #project
#find the user
#user = Project.find params[:project_id]
raise Sinatra::NotFound unless #user
#add user to project's users collection
#project.add_user #user
#send a new representation of the parent resource back to the client
#i like to include the child resources as well
#json might look something like this
#{ 'name' : 'a project name', 'users' : ['/users/:user_id', '/users/:another_user_id'] }
#project.to_json
end

Ruby JSON issue

I know the title is a bit vague, but I dont know what to put on there.
I'm developing an API with Sinatra for our backend in Ruby. The thing is that I need to be able to pass JSON to the service representing a User. The problem I'm facing is that when I run my tests it does not work, but doing it manually against the service it does work. I'm guessing there is an issue with the JSON format.
I've updated my User model to rely on the helpers from ActiveModel for the JSON serialization. I was running in too much problems with manual conversions. This is what the base User model looks like:
class User
include ActiveModel::Serializers::JSON
attr_accessor :login, :email, :birthday, :created_at, :updated_at, :password_sha, :password_salt
# Creates a new instance of the class using the information stored
# in the hash. If data is missing then nill will be assigned to the
# corresponding property.
def initialize(params = {})
return if params.nil?
self.login = params[:login] if params.key?("login")
self.email = params[:email] if params.key?("email")
self.birthday = Time.parse(params[:birthday]) rescue Time.now
if params.key?("password_salt") && params.key?("password_sha")
self.password_salt = params["password_salt"]
self.password_sha = params["password_sha"]
elsif params.key?("password")
self.set_password(params[:password])
end
self.created_at = Time.now
end
def attributes
{:login => self.login, :email => self.email, :birthday => self.birthday, :created_at => self.created_at, :updated_at => self.updated_at, :password_sha => self.password_sha, :password_salt => self.password_salt}
end
def attributes=(params = {})
self.login = params['login']
self.email = params['email']
self.birthday = params['birthday']
self.created_at = params['created_at']
self.updated_at = params['updated_at']
self.password_sha = params['password_sha']
self.password_salt = params['password_salt']
end
end
I'm using Cucumber, Rack::Test and Capybara to test my API implementation.
The code of the API application looks like this:
# This action will respond to POST request on the /users URI,
# and is responsible for creating a User in the various systems.
post '/users' do
begin
user = User.new.from_json(request.body.read)
201
rescue
400
end
end
In the above piece I expect the json representation in the request body. For some reason the params hash is empty here, don't know why
The test section that makes the actuall post looks like this:
When /^I send a POST request to "([^\"]*)" with the following:$/ do |path, body|
post path, User.new(body.hashes.first).to_json, "CONTENT_TYPE" => "application/json"
end
The example output JSON string generated by the User.rb file looks like this:
"{"user":{"birthday":"1985-02-14T00:00:00+01:00","created_at":"2012-03-23T12:54:11+01:00","email":"arne.de.herdt#gmail.com","login":"airslash","password_salt":"x9fOmBOt","password_sha":"2d3afc55aee8d97cc63b3d4c985040d35147a4a1d312e6450ebee05edcb8e037","updated_at":null}}"
The output is copied from the Rubymine IDE, but when I submit this to the application, I cannot parse it because:
The params hash is empty when using the tests
doing it manually gives me the error about needing at least 2 octets.

How can I send emails in Rails 3 using the recipient's locale?

How can I send mails in a mailer using the recipient's locale. I have the preferred locale for each user in the database. Notice this is different from the current locale (I18n.locale), as long as the current user doesn't have to be the recipient. So the difficult thing is to use the mailer in a different locale without changing I18n.locale:
def new_follower(user, follower)
#follower = follower
#user = user
mail :to=>#user.email
end
Using I18n.locale = #user.profile.locale before mail :to=>... would solve the mailer issue, but would change the behaviour in the rest of the thread.
I believe the best way to do this is with the great method I18n.with_locale, it allows you to temporarily change the I18n.locale inside a block, you can use it like this:
def new_follower(user, follower)
#follower = follower
#user = user
I18n.with_locale(#user.profile.locale) do
mail to: #user.email
end
end
And it'll change the locale just to send the email, immediately changing back after the block ends.
Source: http://www.rubydoc.info/docs/rails/2.3.8/I18n.with_locale
This answer was a dirty hack that ignored I18n's with_locale method, which is in another answer. The original answer (which works but you shouldn't use it) is below.
Quick and dirty:
class SystemMailer < ActionMailer::Base
def new_follower(user, follower)
#follower = follower
#user = user
using_locale(#user.profile.locale){mail(:to=>#user.email)}
end
protected
def using_locale(locale, &block)
original_locale = I18n.locale
I18n.locale = locale
return_value = yield
I18n.locale = original_locale
return_value
end
end
in the most resent version of rails at this time it's sufficient to use
"I18n.locale = account.locale"
in the controller and make multiple views with the following naming strategy
welcome.html.erb,
welcome.it.html.erb and e.g.
welcome.fr.html.erb
None of the above is really working since the version 3 to translate both subject and content and be sure that the locale is reseted back to the original one... so I did the following (all mailer inherit from that class:
class ResourceMailer < ActionMailer::Base
def mail(headers={}, &block)
I18n.locale = mail_locale
super
ensure
reset_locale
end
def i18n_subject(options = {})
I18n.locale = mail_locale
mailer_scope = self.class.mailer_name.gsub('/', '.')
I18n.t(:subject, options.merge(:scope => [mailer_scope, action_name], :default => action_name.humanize))
ensure
reset_locale
end
def set_locale(locale)
#mail_locale = locale
end
protected
def mail_locale
#mail_locale || I18n.locale
end
def reset_locale
I18n.locale = I18n.default_locale
end
end
You just need to set the locale before you call the mail() method:
set_locale #user.locale
You can use the i18n_subject method which scope the current path so everything is structured:
mail(:subject => i18n_subject(:name => #user.name)
This simple plugin was developed for rails 2 but seems to work in rails 3 too.
http://github.com/Bertg/i18n_action_mailer
With it you can do the following:
def new_follower(user, follower)
#follower = follower
#user = user
set_locale user.locale
mail :to => #user.email, :subject => t(:new_follower_subject)
end
The subject and mail templates are then translated using the user's locale.
Here's an updated version that also supports the '.key' short-hand notation, so you don't have to spell out each key in its entirety.
http://github.com/larspind/i18n_action_mailer
The problem with the mentioned plugins are that they don't work in all situations, for example doing User.human_name or User.human_attribute_name(...) will not translate correctly. The following is the easiest and guaranteed method to work:
stick this somewhere (in initializers or a plugin):
module I18nActionMailer
def self.included(base)
base.class_eval do
include InstanceMethods
alias_method_chain :create!, :locale
end
end
module InstanceMethods
def create_with_locale!(method_name, *parameters)
original_locale = I18n.locale
begin
create_without_locale!(method_name, *parameters)
ensure
I18n.locale = original_locale
end
end
end
end
ActionMailer::Base.send(:include, I18nActionMailer)
and then in your mailer class start your method by setting the desired locale, for example:
def welcome(user)
I18n.locale = user.locale
# etc.
end

Resources