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

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)

Related

Tracking wins & losses with PSQL/DataMapper

I have a User class which allows people to register and play a game; I also have a Game class, which contains the logic of said game (RPS).
When people register, their information is held in a psql database. The informations is obtained using params. It looks like this:
Class User
attr_reader :weapon
include DataMapper::Resource
property :id, Serial
property :name, String, required: true
property :email, String, required: true, unique: true
property :password_digest, Text
attr_reader :password
attr_accessor :password_confirmation
validates_confirmation_of :password
validates_format_of :email, as: :email_address
has n, :games
The corresponding Game class, contains this DB logic:
class Game
include DataMapper::Resource
property :id, Serial
property :win, ?
property :lose, ?
belongs_to :user
My issue is that I really don't know how to keep a record of how many games the user has won/lost. Do I need (or should I have separate classes for wins and losses? What key type should I use (serial/int)? All I want is for 'win' or 'lose' to increment by one each time the player...well, wins or loses.
All help/knowledge shared is greatly appreciated.
Thanks.
One way to do it could be to add two integer columsn "wins" and "losses" to User, and make helper methods to increment:
property :wins, Integer, default: 0
property :losses, Integer, default: 0
# usage: user.increment(:wins) or user.increment(:losses)
def increment(type)
update({ type => (send(:type) + 1) })
end

Securely pass ID param in URL rails

I have a link like this: http://www.somesite.com/s/bkucoj?i=#{#client.id}.
How do I make sure, that client's id will be passed securely (hashed/encrypted), and not just naked number?
So the goal is to get something like:
http://www.somesite.com/s/bkucoj?i=f1nSbd3bH34ghfAh12lcvzD
instead of
http://www.somesite.com/s/bkucoj?i=12.
How can I achieve it?
And what's more, I would also like to ensure, that on the other end the client ID is gotten correctly
Thank you!
Probably the best way to do this would be to add an extra column (non-null, unique) to your User model, which is randomized upon the User creation.
before_create do
self.uuid = SecureRandom.uuid
end
Then you can use uuid to identify the user instead of id.
Naturally you will need to modify all your existing user when adding this column.
Implementation details
Your migration needs to have 3 parts. Firstly you need to add a uniq, nullable column uuid to users table. Then, you need to loop over your existing customers and populate this column. After it you can make the column not-nullable. It would most likely look like this:
class Blah000000000 < ActiveRecord::Migration
class User < ActiveRecord::Base
before_save { self.uuid ||= SecureRandom.uuid }
end
def up
add_column :users, :uuid, :string, unique: true
User.all.each &:save!
change_column :user, :uuid, string, unique: true, null: false
end
def down
remove_column :users, :uuid
end
end
You could add an extra parameter which is the HMAC or digital signature of the id. The recipient can verify that id has not changed.

FactoryGirl.create doesn't save Mongoid relations

I have two classes:
class User
include Mongoid::Document
has_one :preference
attr_accessible :name
field :name, type: String
end
class Preference
include Mongoid::Document
belongs_to :user
attr_accessible :somepref
field :somepref, type: Boolean
end
And I have two factories:
FactoryGirl.define do
factory :user do
preference
name 'John'
end
end
FactoryGirl.define do
factory :preference do
somepref true
end
end
After I create a User both documents are saved in the DB, but Preference document is missing user_id field and so has_one relation doesn't work when I read User from the DB.
I've currently fixed it by adding this piece of code in User factory:
after(:create) do |user|
#user.preference.save! #without this user_id field doesn't get saved
end
Can anyone explain to me why is this happening and is there a better fix?
Mongoid seems to be lacking support here.
When FactoryGirl creates a user, it first has to create the preference for that new user. As the new user does not have an id yet, the preference can't store it either.
In general, when you try create parent & child models in one operation, you need two steps:
create the parent, persist to database so it get's an id.
create the child for the parent and persist it.
Step two would end up in an after(:create) block. Like this:
FactoryGirl.define do
factory :user do
name 'John'
after(:create) do |user|
preference { create(:preference, user: user) }
end
end
end
As stated in this answer:
To ensure that you can always immediately read back the data you just
wrote using Mongoid, you need to set the database session options
consistency: :strong, safe: true
neither of which are the default.

User relation to group and company MongoMapper

I have need to create a system where a user can login with a user account where a user is a member of a group like admin or editor. Also a user is a member of a company. in both cases of groups and company's they can have multiple users but user can be only a member of one company and multiple groups.
The relations that i can get out of this is that a group has many users, company has many users, user has one company, user has many groups.
but my problem then how do i create this with ruby and mongoMapper? i have look at the documentation and other sources but did not find a good solution or explanation on how to use or set this up.
If anyone have a better way of doing it also welcome.
these are the current classes i have written.
class User
include MongoMapper::Document
key :username, String
key :password, String
key :name, String
belongs_to :group
belongs_to :company
end
class Group
include MongoMapper::Document
key :group_id, Integer
key :name, String
key :accesLevel, Integer
many :user
end
class Company
include MongoMapper::Document
key :name, String
many :user
end
After some more google searching i have found a solution.
first i made the user class look like this:
class User
include MongoMapper::Document
key :username, String
key :password, String
key :name, String
key :companyID
key :groupID
timestamps!
end
then the group and company class like this:
class Company
include MongoMapper::Document
key :name, String
timestamps!
end
class Group
include MongoMapper::Document
key :name, String
key :accesLevel, Integer
timestamps!
end
With these classes in place i changed my controller to first create a company and then a group these could also be loaded but for the ease of testing it was not necessary to do this so that i didn't need to write the code for this.
company = Company.new
company.name = "comp"
group = Group.new
group.name = "admin"
user = User.new
user.name = "user1"
user.username = "user1"
user.password = "passuser1"
user.groupID = group.id
user.companyID = company.id
db_config = YAML::load(File.open('./dbconfig.yml'))
MongoMapper.connection = Mongo::Connection.new(db_config['hostname'])
MongoMapper.database = db_config['name']
company.save
group.save
user.save

Rails Active Record: Calling Build Method Should Not Save To Database Before Calling Save Method

I have a simple user model
class User < ActiveRecord::Base
has_one :user_profile
end
And a simple user_profile model
class UserProfile < ActiveRecord::Base
belongs_to :user
end
The issue is when I call the following build method, without calling the save method, I end up with a new record in the database (if it passes validation)
class UserProfilesController < ApplicationController
def create
#current_user = login_from_session
#user_profile = current_user.build_user_profile(params[:user_profile])
##user_profile.save (even with this line commented out, it still save a new db record)
redirect_to new_user_profile_path
end
Anyyyyyy one have anyyy idea what's going on.
The definition of this method says the following but it's still saving for me
build_association(attributes = {})
Returns a new object of the associated type that has been instantiated with attributes and linked to this object through a foreign key, but has not yet been saved.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one
Ok, I'm sure experienced vets already know this, but as a rookie I had to figure it out the long way...let me see if I can explain this without screwing it up
Although I was not directly saving the user_profile object, I noticed in my logs that something was updating the user model's last_activity_time (and the user_profile model) each time I submitted the form (the user model's last_activity date was also updated when the logged in user did various other things too - I later realized this was set in the Sorcery gem configuration).
Per http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
AutosaveAssociation is a module that takes care of automatically saving associated records when their parent is saved. In my case, the user mode is the parent and the scenario they provide below mirrors my experience.
class Post
has_one :author, :autosave => true
end
post = Post.find(1)
post.title # => "The current global position of migrating ducks"
post.author.name # => "alloy"
post.title = "On the migration of ducks"
post.author.name = "Eloy Duran"
post.save
post.reload
post.title # => "On the migration of ducks"
post.author.name # => "Eloy Duran"
The following resolutions resolved my problem
1. Stopping Sorcery (config setting) from updating the users last_activity_time (for every action)
or
2. Passing an ':autosave => false' option when I set the association in the user model as follows
class User < ActiveRecord::Base
has_one :user_profile, :autosave => false
end

Resources