So I ran into a little issue with validations -- I created a validation to ensure that no users in a database share identical email addresses. Then I created a user in the database. Afterward, I said user = User.find(1) which returned the user I had just created. Then I wanted to change its name so I said user.name = "New Name" and then tried to use user.save to save it back into the database. However, this command isn't working anymore (it returns false instead) and I think it has to do with my uniqueness validation test. Can someone help me with this problem?
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime
# updated_at :datetime
#
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, #says that the name and email attributes are publicly accessible to outside users.
:password, :password_confirmation #it also says that all attributes other than name and email are NOT publicly accessible.
#this protects against "mass assignment"
email_regex = /^[A-Za-z0-9._+-]+#[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+[A-Za-z]$/ #tests for valid email addresses.
validates :name, :presence => true,
:length => {:maximum => 50}
validates :email, :presence => true,
:format => {:with => email_regex},
:uniqueness => {:case_sensitive => false}
validates :password, :presence => true,
:length => {:maximum => 20, :minimum => 6},
:confirmation => true
before_save :encrypt_password
def has_password?(submitted_password)
#compare encrypted_password with the encrypted version of the submitted password.
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
if (user && user.has_password?(submitted_password))
return user
else
return nil
end
end
private
def encrypt_password
if (new_record?) #true of object has not yet been saved to the database
self.salt = make_salt
end
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string) #uses cryptological hash function SHA2 from the Digest library to encrypt the string.
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
end
try save! and see what the exception tells you
Related
I primarily come from a PHP and ASP.NET background. Recently I got involved with Ruby and am starting an interesting relationship with Padrino. Not too much like Rails and not too less like Sinatra.
I am making first serious application using Padrino and it didn't take long to get stuck and would appreciate your help.
The issue with what I believe is with Padrino Admin. I am trying make users login to my website using Facebook and Omniauth.
I have been following this tutorial: Padrino and Omniauth Overview.
The application is hosted at Heroku.
Result: On Facebook login, an account is crated ( in the database ). But when I reach the restricted area, I get redirected back to the login page.
Here is what I have.
app.rb
module PDeen
class App < Padrino::Application
register Padrino::Admin::AccessControl
register SassInitializer
register Padrino::Rendering
register Padrino::Mailer
register Padrino::Helpers
enable :sessions
# get '/' do
# "Welcome to me # internet"
# end
use OmniAuth::Builder do
provider :facebook, 'xxxx', 'yyyy'
# provider :facebook, 'app_id', 'app_secret'
end
set :login_page, "/login" # determines the url login occurs
access_control.roles_for :any do |role|
role.protect "/profile"
role.protect "/admin" # here a demo path
end
# now we add a role for users
access_control.roles_for :users do |role|
role.allow "/profile"
end
get :index do
'Hi'
end
get :login do
slim :'index'
end
get :profile do
content_type :text
current_account.to_yaml
end
get :destroy do
set_current_account(nil)
redirect url(:index)
end
get :auth, :map => '/auth/:provider/callback' do
auth = request.env["omniauth.auth"]
# account = Account.find_by_provider_and_uid(auth["provider"], auth["uid"]) ||
# Account.create_with_omniauth(auth)
#
account = User.first( :provider => auth["provider"], :uid => auth["uid"] )
if ! account.nil?
set_current_account(account)
redirect :existing
end
if account.nil?
# Create account
account = User.new
account.uid = auth['uid']
account.name = auth['name']
account.provider = auth['provider']
account.email = auth['user_info']['email'] if auth['user_info']
account.role = 'users'
account.save
end
set_current_account(account)
#redirect "http://" + request.env["HTTP_HOST"] + url(:profile)
redirect :new
end
get :existing do
'existing'
end
get '/session/test' do
session[:test] = 'This is a test'
end
get '/session/print' do
"You saved: #{session[:test]}"
end
end
end
User.rb
class User
include DataMapper::Resource
# property <name>, <type>
property :id, Serial
property :name, String
property :email, String
property :role, String
property :uid, String
property :provider, String
end
What happens >>
List item
I go to [server]/profile ~> redirects to [server]/login
I click on Facebook ~> takes to the page to accept the app ~> redirects back to the app
I go to [server]/profile ~> redirects to [server]/login
I thought that sessions are not working. In the time I was working on my first PHP app, I had similar session based issue. But it turned out to be that it wroks. That is where the [server]/session/test and [server]/session/print came in.
When I login to the Padriono console in Heroku and use User.all I see the entry.
I also see that the user gets authenticated. Some thing has to be with `
I checked the Padrino admin Accounts modal. I think the important parameters would be id and role.
Have I done some thing wrong?
Thanks in advance. Any help is highly appreciated.
After going through the Padrino source code, I noticed that it is expecting the Account class for Padrino Admin authentication.
I was assuming, I could make any class and just use it. But for the moment, I have modified the Account.rb modal and instead of using User ( above ) I used Account.
I write this just as I got it resolved, so the validation section of the modal is commented out.
class Account
include DataMapper::Resource
include DataMapper::Validate
attr_accessor :password, :password_confirmation
# Properties
property :id, Serial
property :name, String
property :surname, String
property :email, String
property :crypted_password, String, :length => 70
property :role, String
property :uid, String
property :display_name, String
property :provider, String
# # Validations
# validates_presence_of :email, :role
# validates_presence_of :password, :if => :password_required
# validates_presence_of :password_confirmation, :if => :password_required
# validates_length_of :password, :min => 4, :max => 40, :if => :password_required
# validates_confirmation_of :password, :if => :password_required
# validates_length_of :email, :min => 3, :max => 100
# validates_uniqueness_of :email, :case_sensitive => false
# validates_format_of :email, :with => :email_address
# validates_format_of :role, :with => /[A-Za-z]/
# Callbacks
before :save, :encrypt_password
##
# This method is for authentication purpose
#
def self.authenticate(email, password)
account = first(:conditions => ["lower(email) = lower(?)", email]) if email.present?
account && account.has_password?(password) ? account : nil
end
##
# This method is used by AuthenticationHelper
#
def self.find_by_id(id)
get(id) rescue nil
end
def has_password?(password)
::BCrypt::Password.new(crypted_password) == password
end
private
def password_required
crypted_password.blank? || password.present?
end
def encrypt_password
self.crypted_password = ::BCrypt::Password.create(password) if password.present?
end
end
Note that just after the role, I added 3 more fields namely uid, display_name and provider.
It seems as though, uid provder and role are what is important for the access control.
The controller / route are the same except for one minor change. That is the Model name.
if account.nil?
# Create account
account = Account.new
Would be interesting to use own modal with Omniauth and Padrino Admin helpers. But for the moment, this is great!
I use mongo mapper (0.8.6) in my sinatra service. I have one problem with stack level too deep. The problem is that there is conflict of the key "changes" in my model. Here is my model:
class ChangeLog
include MongoMapper::Document
belongs_to :resource
key :changes, Hash, :required => true
key :message, String, :required => true
key :note, String
key :user_uuid, String, :required => true
key :user_name, String, :required => true
timestamps!
end
However, I don't want to rename my key as in this case, it's the right name for my web service. Any suggestions?
changes is an instance method that will tell you what fields have changed since you last saved the document. Here's an example from MongoMapper's documentation
user = User.create(:name => 'John', :age => 29)
puts user.changed? # false
puts user.changes.inspect # {}
user.name = 'Steve'
puts user.changed? # true
puts user.changes.inspect # {"name"=>["John", "Steve"]}
Unfortunately, you're probably going to need to choose a different name for that field. Maybe "adjustments" or "variations" or "differences" or "modifications"?
I'm have a user class that can optionally have a billing address. When I post a payment form, assuming the user has indicated they want to save their billing address details, I want to either create a new address record or update the original one.
I have tried many things but the closest I can get to working code is...
class User
include DataMapper::Resource
property :id, Serial
property :provider, String, :length => 100
property :identifier, String, :length => 100
property :username, String, :length => 100
property :remember_billing, Boolean
has 1, :billing_address
end
class BillingAddress
include DataMapper::Resource
property :first, String, :length => 20
property :surname, String, :length => 20
property :address1, String, :length => 50
property :address2, String, :length => 50
property :towncity, String, :length => 40
property :state, String, :length => 2
property :postcode, String, :length => 20
property :country, String, :length => 2
property :deleted_at, ParanoidDateTime
belongs_to :user, :key => true
end
post "/pay" do
#post = params[:post]
#addr = params[:addr]
if #addr == nil
#addr = Hash.new
end
user = User.first(:identifier => session["vya.user"])
user.remember_billing = !!#post["remember"]
if user.remember_billing
user.billing_address = BillingAddress.first_or_create({ :user => user }, #addr)
end
user.save
...
which works fine when there is no record. But if there is already a record, it keeps the original values.
I saw a similar post
DataMapper: Create new record or update existing
but if I alter the code to be
user.billing_address = BillingAddress.first_or_create(:user => user).update(#addr)
I get the error
DataMapper::ImmutableError at /pay
Immutable resource cannot be modified
Any help much appreciated
You're chaining lots of things together, there. How about:
billing = BillingAddress.first_or_new(:user => user, #addr) #don't update, send the hash as second parameter
billing.saved? ? billing.update(#addr) : billing.save
raise "Billing is not saved for some reason: #{billing.errors.inspect}" unless billing && billing.saved?
user.billing_address = billing
user.save
When I define the User has_many meetings, it automatically creates a "user_id"
key/value pair to relate to the User collections. Except I can't run any
mongo_mapper finds using this value, without it returning nil or [].
Meeting.first(:user_id => "1234")
Meeting.all(:user_id => "1234")
Meeting.find(:user_id => "1234")
All return nil. Is there another syntax? Basically I can't run a query on the automatically generated associative ObjectId.
# Methods
class User
include MongoMapper::Document
key :user_name, String, :required => true
key :password, String
many :meetings
end
class Meeting
include MongoMapper::Document
key :name, String, :required => true
key :count, Integer, :default => 1
end
# Sinatra
get '/add' do
user = User.new
user.meetings "foobar") #should read: Meeting.new(:name => "foobar")
user.save
end
get '/find' do
test = Meeting.first(:user_id => "4b4f9d6d348f82370b000001") #this is the _id of the newly create user
p test # WTF! returns []
end
As Jimmy mentioned about checking Meeting.all, I don't think you would have anything.
Based on your example above I see a couple potential issues.
- Your User requires a :user_name so it's not getting saved
- would never get saved, because you didn't set the name which is required
- Your Meeting isn't getting saved either
- One more thing, you need to concat your meeting to user.meetings
This works with mongo_mapper 0.6.10
require 'rubygems'
require 'mongo_mapper'
MongoMapper.database = "meetings"
class User
include MongoMapper::Document
key :user_name, String, :required => true
key :password, String
many :meetings
end
class Meeting
include MongoMapper::Document
key :name, String, :required => true
key :count, Integer, :default => 1
end
user = User.create(:user_name => "Rubyist")
user.meetings << Meeting.create(:name => "foobar")
user.save
Meeting.first(:user_id => user.id)
User.find(user.id).meetings
You may have figured this out already, but I hope this is helpful anyway.
You can try using
Meeting.find_by_user_id "1234"
Also, if you run script/console then does Meeting.all show each record as having a user_id assigned to it?
What about just User.find("1234").meetings ?
I'm just starting out with tests. When I run this one:
rake test test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
test "should not save without an email address" do
user = User.new
assert_not user.save
end
end
I get the following error:
1) Error:
UserTest#test_should_not_save_without_an_email_address:
ActiveRecord::StatementInvalid: SQLite3::ConstraintException: NOT NULL constraint failed: groups.name: INSERT INTO "groups" ("created_at", "updated_at", "id") VALUES ('2015-08-11 17:31:07', '2015-08-11 17:31:07', 980190962)
This is user.rb
class User < ActiveRecord::Base
has_many :groups
has_many :user_groups
attr_accessor :password
EMAIL_REGEX = /A[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\z/i
validates :password, :confirmation => true #password_confirmation attr
validates_length_of :password, :in => 6..20, :on => :create
validates :email, :presence => true, :uniqueness => true, :format => EMAIL_REGEX
before_save :encrypt_password
after_save :clear_password
def encrypt_password
if password.present?
self.salt = Digest::SHA1.hexdigest("# We add {self.email} as unique value and #{Time.now} as random value")
self.encrypted_password = Digest::SHA1.hexdigest("Adding #{self.salt} to {password}")
end
end
def clear_password
self.password = nil
end
end
This is test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
As far as I can tell I don't have any callback or otherwise that would attempt to write to the "groups" table. My "groups.yml" is default, but that shouldn't matter if I'm only testing this one model, correct? Any help as to where I could start looking would be much appreciated. Thanks!
test_helper.rb was setting up all my fixtures and they weren't defined. Commenting "fixtures :all" fixed it.