FactoryGirl.create doesn't save Mongoid relations - ruby

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.

Related

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)

Activerecord/Datamapper - Have one child belong to many parents

How would you set up an activerecord/datamapper association for the following scenario:
A user creates a "bookshelf" which has many books(a book object just has an isbn that is used to query an api, and has_many review objects associated with it). Let's say Jack creates a "bookshelf" with a book object. Then, lets say that Jill creates a "bookshelf" with the same book object(it has the same id and the same reviews). The book object has the following code as of now:
class Book < ActiveRecord::Base
has_many :reviews
end
Then, when you view the page for a book (you click the link to it from the "bookshelf" created by Jack) you should see the same book object when you clicked the link to it from Jill's "bookshelf" (e.g. both "bookshelves" have a link to /books/23 because they have the same book object).
I have not been able to figure this out with the has_many association because that requires me to make a new book each time a user adds a book to their "bookshelf." I have trouble understanding the has_and_belongs_to_many relationship, is that what should be used here? I was not able to find any similar questions on SO, so any help is greatly appreciated.
I am using Rails 4 with Ruby 2.1.
Here is a drawing of what I would like to accomplish:
Drawing
Yes, you would have to define many-to-many relationship between a Bookshelf and a Book. There are two ways to achieve this in Rails:
Option 1) Use has_and_belongs_to_many
See guide
According to official documentation has_and_belongs_to_many association:
Specifies a many-to-many relationship with another class. This associates two classes via an intermediate join table. Unless the join table is explicitly specified as an option, it is guessed using the lexical order of the class names. So a join between Developer and Project will give the default join table name of “developers_projects” because “D” precedes “P” alphabetically.
So, your classes should look like this:
class Bookshelf < ActiveRecord::Base
has_and_belongs_to_many :books
end
class Book < ActiveRecord::Base
has_and_belongs_to_many :bookshelves
has_many :reviews
end
Add a join table generation to your migrations:
class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
def change
create_table :books_bookshelves, id: false do |t|
t.belongs_to :book, index: true
t.belongs_to :bookshelf, index: true
end
end
end
This will create a books_bookshelves table in your database. The table will have no primary key. There would be two foreign keys to your models Book and Bookshelf.
So, if you call self.books in the context of an user's bookshelf, you will get a list of books in the bookshelf. Vice versa, calling self.bookshelves in the context of a book will return a set of bookshelves the book belongs to.
The problem with this approach is that every time you add a new book to the bookshelf a new record is created in the database. If you are okay with that, there is no easier option than using has_and_belongs_to_many association. Otherwise, I recommend you to go with the Option #2.
Option 2) Use has_many :through
Another option is to use has_many, :through association (see guide). You would have to define one more model to do that, but it might come handy in some use cases (see below for an example).
Your classes should look like this:
class Bookshelf < ActiveRecord::Base
has_many :books, through: :books_bookshelves
has_many :books_bookshelves
end
class Book < ActiveRecord::Base
has_many :bookshelves, through: :books_bookshelves
has_many :books_bookshelves
has_many :reviews
end
class BooksBookshelf < ActiveRecord::Base
belongs_to :book
belongs_to :bookshelf
end
Probably the best thing about using has_many :through association is that it allows you to add custom columns to the join table (e.g. add column count to keep track how many books of the same type are there in the bookshelf).
The migration would look pretty much the same as the one we used in Option 1, except for the fact we are adding an unique constraint on the foreign keys (please note that adding the constraint is optional):
class CreateBooksBookshelvesJoinTable < ActiveRecord::Migration
def change
create_table :books_bookshelves, id: false do |t|
t.belongs_to :book, index: true
t.belongs_to :bookshelf, index: true
# add your custom columns here
end
add_index :books_bookshelves, [:book_id, :bookshelf_id], unique: true # to make sure you won't create duplicate records
end
end
By going with this approach, adding a new would be a bit more complicated as you would have to make sure you are not inserting duplicate records in the join table. (However, you may remove the unique constraint from the migration, to achieve exactly the same kind of behavior as you would get with has_and_belongs_to_many.)

Access attribute in Active Record 'through' association object

I have two classes, User and Product in a 'many-to-many through' association, using the class Prole (for product role).
class User < ActiveRecord::Base
has_many :proles
has_many :products, through: :proles
end
class Product < ActiveRecord::Base
has_many :proles
has_many :users, through: :proles
end
class Prole < ActiveRecord::Base
# has an attribute called 'role'
end
prole has an attribute called role which I'd like to use to qualify the user-product association.
The association works fine, but I can't figure out how to access the role attribute after creating the association. For example, if I do:
user.products << product
how can I access the attribute in the prole object just created?
I guess I could iterate through the prole objects and find the correct one, but I'm hoping there's a cleaner way.
Is this possible? Any hints?
TIA.
I was hoping for something a little more direct, but here's a
POSSIBLE ANSWER:
prole = Prole.find_by user_id: user.id, product_id: product.id
or even better
prole = user.proles.where("product_id = #{product.id}")
After some testing, it looks like the easiest way to grab specifically the Prole object that was just created is by querying by the two foreign keys directly against the Prole model, as suggested in your possible answer.
Prole.find_by(user_id: user.id, product_id: product.id)
If you want it as an association on the user object, you could use the includes approach to do eager loading, but it will still load every prole for the user in question
# specifying proles: {product_id: product.id} in the where clause here
# only limits users retrieved, not proles
user = User.includes(:proles).where(id: user.id)
# eager-loaded prole array
user.proles.find { |prole| prole.product_id == product.id }
See this answer for more info on that. But it looks like your possible answer is the cleanest way.

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.

Attempting to create a database item using the has_one relationship, no exceptions, but still no item

Models:
A User has_one Ucellar
A Ucellar belongs_to User
I have confirmed from multiple sources that these are set up correctly. For posterity, here is the top portion of those two models.
class User < ActiveRecord::Base
has_many :authorizations
has_one :ucellar
validates :name, :email, :presence => true
This is actually the entire Ucellar model.
class Ucellar < ActiveRecord::Base
belongs_to :user
end
Ucellar has a column called user_id, which I know is necessary. The part of my application that creates a user uses the method create_with_oath. Below is the entire User class. Note the second line of the create method.
class User < ActiveRecord::Base
has_many :authorizations
has_one :ucellar
validates :name, :email, :presence => true
def create
#user = User.new(user_params)
#ucellar = #user.create_ucellar
end
def add_provider(auth_hash)
# Check if the provider already exists, so we don't add it twice unless authorizations.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
Authorization.create :user => self, :provider => auth_hash["provider"], :uid => auth_hash["uid"]
end
end
def self.create_with_omniauth(auth)
user = User.create({:name => auth["info"]["name"], :email => auth["info"]["email"]})
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
EDIT:
Forgot to summarize the symptoms. On create, the user is in the db, with no exceptions thrown, and nothing to signify that anything went wrong. However, the related ucellar is never created. Per the documentation Here, the create method should create AND save the related ucellar.
It should create ucellar too.
Try to get the error messages after the creation by calling:
raise #user.errors.full_messages.to_sentence.inspect
I'm not sure why this wasn't working, but I ended up just moving this code out of the create action of the user controller, and putting it directly after an action that was creating a user. It solved my issue though. Thanks everyone for your help!

Resources