DataObject::Integrity error in DataMapper - ruby

I wrote an app where I plan to save user data via DataMapper and sqlite3, and inventory items (several of them) using GDBM. When I run this with irb, User.new and inventory methods works fine however DataMapper says that my user model is not valid and does not save it. I worked with DataMapper previously and never encountered such an error, in fact I used the same user model (with password salt and hash) without any errors in a sinatra-datamapper app.
To be clear, I intend to save both the username and 'the path to the gdbm database file' as a string in datamapper. The gdbm creates its own db file and all methods work fine without complaints and all data is persistent. Since I am not trying to save the gdbm object but only a string in datamapper, I think there should be no problem of validation.
DataMapper::Model.raise_on_save_failure = true does not give a specific description of the error but save! method gives the following error:
DataObjects::IntegrityError: users.name may not be NULL (code: 19, sql state: , query: INSERT INTO "users" DEFAULT VALUES, uri: sqlite3:/home/barerd/game_inventory/users.db?scheme=sqlite3&user=...... but name attribute is not empty as I checked in irb.
Maybe the error originates from sqlite but I have no knowledge to test that. Can someone guide me how to inspect this error?
require 'gdbm'
require 'data_mapper'
require 'dm-validations'
DataMapper.setup :default, 'sqlite://' + Dir.pwd + '/users.db'
class User
attr_reader :name, :inventory_db
include DataMapper::Resource
property id, Serial
property :name, String, :required => true, :unique => true, :messages => { :presence =>...... etc }
property :inventory_db, String
def initialize name
#name = name
#inventory_db = Dir.pwd + '/#{name}_inventory.db'
end
def inventory
GDBM.new #inventory_db, 0644, GDBM::WRCREAT
end
..several methods related to inventory..
end
DataMapper.finalize.auto_migrate!

Googling further, I found out that
there is a guard around defining property getter in dkubb/dm-core
via this link. After removing getter methods from the model, everything worked fine.

Related

How to implement Application Record in ruby without rails

I've been looking at this repository
https://github.com/stungeye/ActiveRecord-without-Rails to understand how can I implement activerecord without rails.I got some problems. At first I got this error when I tried to run this class:
require 'active_record'
ActiveRecord::Base.establish_connection(adapter: 'mysql2', database: 'rbuserroom')
# Can override table name and primary key
class User < ActiveRecord::Base
self.primary_key = 'user_id'
def initialize(id, email)
#user_id = id
#user_email = email
#user_room
end
def create()
self.save
end
# accessor get and set method
attr_accessor :user_room
attr_reader :user_id, :user_email
end
usr = User.new(1, "user#user")
usr.create()
but I got this error:
Traceback (most recent call last):
1: from -:25:in `<main>'
/home/felipe/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/inheritance.rb:52:in `new': wrong number of arguments (given 2, expected 0..1) (ArgumentError)
it seems that active record doesn't accept the parameters in the creation of the class, in fact after that i noticed that the classes in this example don't contain anything inside, how would active record define the columns of the tables?
i'm used to java jpa and springboot that i have to define all the attributes of the class.
besides i don't know if the active record is really working.
I just want that when I create a new user with my user class, the information persists in the database as an insert, or that it updates when I make a change to my object attribute value.
With ActiveRecord you don't need to specify the column names. It detects them from the DB.
You can just write:
require 'active_record'
ActiveRecord::Base.establish_connection(adapter: 'mysql2', database: 'rbuserroom')
class User < ActiveRecord::Base
self.primary_key = 'user_id'
end
usr = User.create(user_id: 1, user_email: "user#user")
You can read more about creating models in the docs. Especially in 3 Creating Active Record Models

Why won't my DataMapper record save when I pass it the user it belongs to?

I have set up a simple has-many and belongs-to association using DataMapper with Sinatra. My User model has many 'peeps', and my Peep model belongs to user. See below for the classes....
I am able to successfully create a new peep which belongs to a particular user, by passing the user_id directly into the peep on initialization, like this:
Method 1
new_peep = Peep.create(content: params[:content], user_id: current_user.id)
This adds 1 to Peep to Peep.count.
However, my understanding is that I should be able to create the association by assigning the current_user to new_peep.user. But when I try that, the peep object won't save.
I've tried this:
Method 2
new_peep = Peep.create(content: params[:content], user: current_user)
Current user here is User.get(session[:current_user_id])
The resulting new_peep has an id of nil, but does have user_id set to the current_user's id. New_peep looks exactly like the new_peep that successfully gets created using Method 1, except it has no id because it hasn't successfully saved. I've tried calling new_peep.save separately, but I still get the below for the peep object:
<Peep #id=nil #content="This is a test peep" #created_at=#<DateTime: 2016-05-08T12:42:52+01:00 ((2457517j,42172s,0n),+3600s,2299161j)> #user_id=1>, #errors={}
Note that there are no validation errors. Most problems other people seem to have had with saving records come down to a validation criteria not being met.
I assumed this was something to do with the belongs_to association not working, but I can (after creating new_peep using Method 1 above) still call new_peep.user and access the correct user. So it seems to me the belongs_to is working as a reader but not a setter.
This problem also means I cannot create a peep by adding one into the user.peeps collection then saving user, which means there's virtually no point in peep belonging to user.
I've seen other people have had problems saving records that don't have any changes to save - but this is a completely new record, so all its attributes are being updated.
I'd really like to know what's going on - this has baffled me for too long!
Here are my classes:
class Peep
include DataMapper::Resource
property :id, Serial
property :content, Text
property :created_at, DateTime
belongs_to :user, required: false
def created_at_formatted
created_at.strftime("%H:%M, %A %-d %b %Y")
end
end
class User
include DataMapper::Resource
include BCrypt
attr_accessor :password_confirmation
attr_reader :password
property :id, Serial
property :email, String, unique: true, required: true
property :username, String, unique: true, required: true
property :name, String
property :password_hash, Text
def self.authenticate(params)
user = first(email: params[:email])
if user && Password.new(user.password_hash) == params[:password]
user
else
false
end
end
def password=(actual_password)
#password = actual_password
self.password_hash = Password.create(actual_password)
end
validates_confirmation_of :password
validates_presence_of :password
has n, :peeps
end
When you create the Peep you don't create it through User, maybe that's why it has no primary id, since it belongs to User. Also you're assigning it the foreign key user_id as if you have a property defined as such. Although the database has it, in DataMapper you don't pass in the foreign key id, it does it for you.
Try replacing
new_peep = Peep.create(content: params[:content], user: current_user)
with:
new_peep = current_user.peeps.create(content: params[:content], created_at: Time.now)

Can I change SQLite column name with DataMapper?

Brand new to DataMapper and wondering if I can use DataMapper.auto_updgrade! to change a column name of an existing column in a SQLite database?
If I have the following in a song.rb
require 'date'
require 'dm-core'
require 'dm-migrations'
DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/development.db")
class Song
include DataMapper::Resource
property :id, Serial
property :title, String
property :music_by, String
property :lryics_by, String
property :lyrics, Text
property :length, Integer
property :released_on, Date
def released_on=date
super Date.strptime(date, '%m/%d/%Y')
end
end
DataMapper.finalize
After a Song.auto_migrate!
2.0.0-p598 :004 > Song.new
=> #<Song #id=nil #title=nil #music_by=nil #lyrics_by=nil #lyrics=nil #length=nil #released_on=nil>
Is it possible to change the
property :lryics_by, String
to
property :words_by, String
and have the database column name change, but keep any existing data?
I've tried with Song.auto_upgrade! and it adds an empty new column and leaves the original column and data in place. On the other hand, my Song.new object looks right.
2.0.0-p598 :004 > Song.new
=> #<Song #id=nil #title=nil #music_by=nil #words_by=nil #lyrics=nil #length=nil #released_on=nil>
It seems like I need a migration in the way that ActiveRecord (I've played around a little with that ORM) handles migrations. Or I would need to change the column name with SQL or an app or the Firefox SQLlite plugin.
UPDATE:
I'm wondering now if this is more a SQLite thing than a DataMapper thing. When I went to delete a column in Firefox's SQLite Manager plugin I got this message:
This is a potentially dangerous operation. SQLite does not support statements that can alter a column in a table. Here, we attempt to reconstruct the new CREATE SQL statement by looking at the pragma table_info which does not contain complete information about the structure of the existing table.
Do you still want to proceed?
dm-migrations can do this but not for SQLite, mostly because SQLite doesn't support renaming columns, as it has a limited ALTER TABLE implementation (http://www.sqlite.org/lang_altertable.html)
There's a rename_column migration but as you can see from the dm-migrations TableModifier class (https://github.com/datamapper/dm-migrations/blob/master/lib/dm-migrations/sql/table_modifier.rb), it's not available for SQLite:
def rename_column(name, new_name, opts = {})
# raise NotImplemented for SQLite3
#statements << #adapter.rename_column_type_statement(table_name, name, new_name)
end

Datamapper "Update" does not update record

I am having trouble with datamapper not updating a model. I can create and save models without issue. I have enabled raise_on_save_failure and checked the return value of update but see no errors.
Here is the model:
class UserProfile
include DataMapper::Resource
attr_accessor :id, :wants_hints, :is_beta_user
property :id, Serial #auto-increment integer key
property :is_beta_user, Boolean
property :wants_hints, Boolean
has 1, :user, :through => Resource
end
And here is where it is updated in the controller:
if user = User.get(request.session[:user])
if request.params[:user_profile]
beta = request.params[:user_profile].has_key?('is_beta_user')
hints = request.params[:user_profile].has_key?('wants_hints')
user.user_profile.update({:is_beta_user => beta, :wants_hints => hints}) # returns true
Log.puts user.user_profile.errors.each {|e| Log.puts e.to_s} # returns empty list []
end
end
When the controller is called update always returns true, and there are never errors in the error list. The datamapper log, which is set to :debug, only shows the SELECT queries for retrieving the user and user_profile and that is all. Why would I be able to save a newly created model, but not be allowed to update that same model?
Removing attr_accessor fixed the problem. From my research attr_accessor is used for attributes not in the database.
DataMapper's save and update do not necessarily produce an UPDATE sentence. It will only do so if the data held by the model object has changed. So, for example, in the following code the update will return true but will not produce an UPDATE:
# This generates an INSERT
user = User.create(:login => 'kintaro', :email => 'kintaro#example.com')
# This does NOT generate an UPDATE
user.update(:login => 'kintaro')
If you do this, however, an UPDATE will be produced:
# This generates an UPDATE
user.update(:login => 'kintaro22')
Maybe this is what's happening?

Rails -- self vs. #

I am following Michael Hartl's RoR tutorial, and it is covering the basics of password encryption. This is the User model as it currently stands:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email,: password, :password_confirmation
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
private
def encrypt_password
self.encrypted_password = encrypt(password)
end
def encrypt(string)
string
end
end
I posted a previous question about before_save not working, and it turns out that what I had accidentally done is written my encrypt_password as:
def encrypt_password
#encrypted_password = encrypt(password)
end
I understand that if self.encrypted_password sets the encrypted_password attribute, but why does #encrypted_password not do that as well? In the response to the previous post about before_save not working someone said that the instance variable was "forgotten" after the method ended with the way I had originally coded it -- why was this the case? Can someone please explain how self and # work differently in the context of the code above?
NOTE: I already took a look at the posts here and here, but they both say that "self" is calling the attribute = method, and I don't even understand how that method could exist here since I never created it or declared the encrypted_password w/ attr_accessor. So I am still confused, and this is not a re-posting of those questions.
The accessors for encrypted_password have been automatically added by Rails for you because a field by that name exists in the users table.
Any field you add to a table will be automatically made available via self.field_name.
Here is where Michael Hartl's tutorial creates the encrypted_password field in the users table.
Also look at the user_spec.rb (Listing 7.3) in the linked page, where the author is testing for the presence of the encrypted_password field.
UPDATED:
As #mu points out, the # is used for Ruby instance variables (aka "iv"). But encrypted_password is an "attribute" defined by Rails, and is not an instance variable.
If you run User.find(1).instance_variables, you will see that there is an iv called #attributes, which is of type Hash.
Inside that iv is where the encrypted_password is stored. Rails has defined accessor methods for encrypted_password, which gets/sets the data for that
attribute in the #attributes Hash.
Note that you could also get/set the data via #attributes["encrypted_password"] called from within the User class (but the accessor methods are convenient way to do just that).
If you let me, I'd like to rephrase the answer.
I explained in this post, that as soon as you create a (rails-) Model with the same (singular) name as one of the (plural) tablenames of your database, the "magic" of rails will create setters and getters in order to modify your table's records.
This is because your model inherits all methods from the ActiveRecord::Base Class, which defines basic CRUD accessors (Create, Read, Update, Delete).
The key point related to your question, is that you don't know how rails implements the instance variable related to your database table column, And you shouldn't. :) All you have to know is that at that point, you have setters and getters available to CRUD (create, read, update, delete) your database column "encrypted_password".
In your example, maybe rails uses an instance variable called #encrypted_password, maybe rails uses an hash-instance-variable called #attributes["encrypted_password"], or maybe rails uses an instance variable called #you_will_never_guess_encrypted_password.
-
And that's a good point you don't know about the internal rails behavior with instance variables. In 2019 Rails further development may lead the framework to use #complicated-hash-instance-variable to store the encrypted_password value.
In fact the best approach is to let rails manage its "private" "affair" ;) with instance variables, and just use the getter and setter methods it provides to you.
So your application will still work with encrypted_password in the next century (I hope so ^^).
So if you use #encrypted_password it may work with some "imaginary" version of rails and it won't work anymore with other rails versions. Actually with a current version of rails it doesn't work.
-
The second key point is that when you want to use the getter "encrypted_password" Rails created for your encrypted_password database table column, you prefix it with "self" in order to tells Ruby : "ok I want to use the encrypted_password method of my User instance variable."
In Ruby, a method is called by passing its name to a receiver.
You write it like this :
my_receiver.my_method
In your case we pass the method encrypted_password to the User instance variable. But we don't know how this instance variable will be named, so we use the word self to tell Ruby : "I'm talking about any instance variable of the User class that calls the encrypted_password method".
For instance we could have named our instance variable "toto" :
toto = User.new
so toto.encrypted_password would display the encrypted password, and self in this very case in our code would reference toto.
However, thanks to Ruby, if you don't give any receiver when calling a method, Ruby will assume you pass it to self.
Reference : Pragmatic Programmer's guide
So in your example, you even don't need to put "self." as prefix.
You could have it written like this :
class User < ActiveRecord::Base
def encrypt_password
encrypted_password = encrypt(password)
end
end
I hope this helps to clarify this interesting subject.
TL;DR -
Always write self.widget_count = 123 if you intend to save widget_count back to the database.
(But please do read the long answers, as the reason why is valuable to know.)

Resources