find_or_create_by using mongoid and sidekiq leads to duplication - ruby-on-rails-4

Rails 4.0.2, Mongoid git alpha, sidekiq 2.17.0
Using a worker to parse a CSV file and put the contents into mongo, but running into several issues... Most obvious is a product with the same name end up as duplicate documents while using find_or_create_by.
class Category
include Mongoid::Document
field :title, type: String
has_many :products
end
Class Product
include Mongoid::Document
field :name, type: String
belongs_to: :category
end
Class ProductWorker
include Sidekiq::Worker
def perform(category, name)
category = Category.find_or_create_by( title: category )
product = Product.find_or_create_by(name: name)
product.category = category
product.save
end
end
With a CSV file with only two products in the same category, I'll end up with two entries for category with the same name, each product in separate categories. When I remove sidekiq async and perform it on the model directly I get the correct result of 1 category with two relational products.
It makes sense that if the workers perform the find_and_create_by fast enough, both workers will find nil and thus create new objects. How can I prevent this?

Uniqueness validation is not enough, because they are defined at model level by Mongoid.
The only way to guarantee uniqueness is defining unique indexes:
class Category
include Mongoid::Document
field :title, type: String
has_many :products
index({ title: 1 }, background: true, unique: true)
validates_uniqueness_of :title
end

Looks like it's missing a uniqueness validation.
class Category
include Mongoid::Document
field :title, type: String
has_many :products
validates_uniqueness_of :title # Won't duplicate records
end
You can also use a different, more accurate querying:
Category.where(title: category).first_or_create!
This way you can even rescue if something else goes wrong.

Related

Ruby on Rails - Edit/Update creating new records instead of updating

I have seen the other links to this similar question but none of them seem to work in my case.
My update action is creating new records in mysql database
My app consists of 3 models
FashionModel
ModelProfile and
Measurement
They are defined as follows:
class FashionModel < ActiveRecord::Base
has_secure_password
has_one :model_profile
has_one :measurement
accepts_nested_attributes_for :model_profile
accepts_nested_attributes_for :measurement
end
class ModelProfile < ActiveRecord::Base
belongs_to :fashion_model
end
class Measurement < ActiveRecord::Base
belongs_to :fashion_model
end
The fashion_model_controller.rb is as follows:
class FashionModelsController < ApplicationController
def new
#fashion_model = FashionModel.new
end
def create
#fashion_model = FashionModel.new(fashion_models_sign_up_params)
if #fashion_model.save
flash[:success] = "Welcome to Meriad"
session[:fashion_model_id] = #fashion_model.id
redirect_to edit_fashion_model_path(#fashion_model)
else
render 'new'
end
end
def edit
#fashion_model = FashionModel.find(params[:id])
#fashion_model.build_model_profile
#fashion_model.build_measurement
end
def update
#fashion_model = FashionModel.find(params[:id])
if #fashion_model.update(fashion_models_edit_params)
flash[:success] = "Saved!"
redirect_to fashion_model_path(#fashion_model)
else
render 'edit'
end
end
def show
#fashion_model = FashionModel.find(params[:id])
end
end
The fashion_model_edit_params are
def fashion_models_edit_params
params.require(:fashion_model).permit(:first_name,
:last_name,
:email,
:password,
model_profile_attributes: [:id,
:location,
:bio, :gender,
:phone_number,
:rate,
:profile_image,
:birthdate],
measurement_attributes: [:id,
:feet,
:inches,
:bust,
:waist,
:hips,
:dress,
:shoes,
:hair,
:eyes])
end
I want something on these lines:
The fashion model signs up to the app through new.html.erb (which is stored in the fashion_models table)
The index.html.erb contains a list of all fashion_models with an option to edit information (update their profile)
The edit.html.erb contains fields from the model_profiles table as well as the measurements table, both of which have the foreign keys to the fashion_models table.
My fashion_models/new.html.erb template is pretty straightforward containing first_name, last_name, email, password.
My fashion_models/edit.html.erb template is something like:
<%= form_for #fashion_model do |f| %>
# fashion_model fields here
<%= f.fields_for :model_profile do |t| %>
# model_profile fields here
<% end %>
<%= f.fields_for :measurement do |t| %>
# measurement fields here
<% end %>
<% end %>
Now, whenever I edit a fashion_model/:id, the model_profile and measurement create a new record in the database rather than updating the existing record. Also, when I am on the Edit Profile page, none of the fields pre populate with the existing data. I have to manually enter all the data again.
First, I thought it was because of the build methods, but when I remove them, the fields_for do not display.
Appreciate any help!
You're probably making a mess with their routes.
It works like this:
form_for calls #fashion_model.new_record?
IF True: It will submit to POST /fashion_model, and create an object.
IF False: It will submit to PUT /fashion_model/:id, and update an object.
As you can see, it just depends if the object already exists in the database or not. So, check again, where you are using the form_for and which object you are passing it.
For questions with the routes, see: http://guides.rubyonrails.org/routing.html
So I finally figured out a way to do this. Hope it helps someone.
My problem was that I had 2 fields_for inside one form_for and I needed to update 2 models with to these forms.
I tired using callbacks like after_action :create_model_profile and after_action :create_measurement. And in both of these methods I tried to save fashion_model_id as the :id of the newly created fashion_model.
But since callbacks are considered as code smells, I wanted a better solution.
Instead of creating a form_for #fashion_models, I wrote a helper method as follows
# app/helpers/form_helper.rb
module FormHelper
def setup_fashion_model(fashion_model)
fashion_model.model_profile ||= ModelProfile.new
fashion_model.measurement ||= Measurement.new
fashion_model
end
end
and in my form_for I used something like
form_for(setup_fashion_model(#fashion_model)) do |f|
...
end
This created a new instance of ModelProfile and Measurement if it did not persist in the database.
It then of course solved the problem of pre populating the fields which contained data.
This was it. Simple solution, but I'd been there scratching my head.
Hope it helps.

Access rails model attributes through a has one association

My User has one Driver:
class User < ActiveRecord::Base
has_one :driver, :dependent => :destroy
end
class Driver < ActiveRecord::Base
belongs_to :user
end
User attributes include, user_first_name, user_last_name, user_email and user_phone number.
I want to be able to access these User attributes when working with the driver class.
For example:
#drivers = Driver.where( :user_first_name => "David )
Obviously the above code won't work, since user_first_name is not a Driver attribute, but is there a way to do this without looping through each user record?
I'm still new to Rails but hopefully this is helpful!
If you create a foreign key index to associate your model's tables within the database, then you should be able to access the data like so...
#user = User.find(params[:id])
#drivers = Driver.where( #user.user_first_name => "David" )
Also, not sure if it was a typo here, but you forgot to close the quotes with "David.

Mongoid belongs_to specify foreign_key

I'd like to set up a relationship between two Mongoid models with has_many and belongs_to and specify a foreign key, like so:
class Author
include Mongoid::Document
field :serial_num, :type => Integer
field :author_name, :type => String
has_many :books
end
class Book
include Mongoid::Document
field :serial_num, :type => Integer
field :book_name, :type => String
belongs_to :author, foreign_key: 'serial_num'
end
This doesn't work, however. My IRB output:
irb :001> b = Book.first
=> #<Book _id: 1, serial_num: "12345", book_name: 'something', author_id: nil>
irb :002> b.author
=> nil
Is it possible to specify 'serial_num' as a foreign key for this relationship, or am I stuck with author_id?
Many thanks.
You need to set both foreign_key and primary_key given your field definitions. The code you gave does respect primary_key but on author this value is looked up in _id, not serial_num. See the documentation for further details and examples.
belongs_to :author, foreign_key: 'serial_num', primary_key: 'serial_num'
I know this is an old post, but I'm guessing it has to do with the field type the foreign key is stored as. If it's not a BSON ObjectId, then it may not return anything.

ActiveRecord - Display columns from multiple associations

I have a user, user_profile and profile_type models. A user has_many user_profiles, a user_profile belongs_to a user and belongs_to a profile_type and a profile_type has_many user_profiles.
I have been reading on how to get this work but I am having problems figuring this out and any help would be much appreciated.
I know I could do this with SQL with a statement like this (freehand SQL, excuse mistakes), but I want to use ActiveRecord.
Select up.id, u.user_id, pt.connection_type
from user u
join user_profile up
on u.user_id = up.user_id
join profile_type pt
on pt.profile_type_id = up.profile_type_id
where u.username = "test"
I want to return nested user_profile objects for an associated user but I want the user_profile object to contain the profile_type.connection_type instead of the profile_type.profile_id.
Currently, if I do this,
user.user_profiles.all
add then iterate through the nested user_profiles that are returned, this is my output:
{
:id
:user_id
:profile_type_id
}
What I want is:
{
:id
:user_id
:profile_type.connection_type (instead of profile_type.profile_type_id)
}
User Model
class User < ActiveRecord::Base
has_many :user_profiles, autosave: true
has_many :account_settings, autosave: true
end
User_Profile Model
class UserProfile < ActiveRecord::Base
belongs_to :user
belongs_to :profile_type
end
User Profile Type Model
class ProfileType < ActiveRecord::Base
has_many :user_profiles
end
Try this:
user.account_settings.select("profile_type.*, profile_type.connection_type").all
I was able to figure out how to do this using Grape.
Since the association was already created, I can use Grape entities to expose what I needed out of the associations. It works seamlessly and I hope this helps anyone else who is having a similar problem.
To get what I was looking for, I needed to gather all user_profiles
userprofiles = user.user_profiles.all
Then, I presented this using Grape
present :user_profile_settings, userprofiles, with: API::V1::Entities::UserProfile
And, here is what my entities looked like:
class UserProfile < Grape::Entity
expose :profile_type, using: ProfileTypeEntity
end
class ProfileTypeEntity < Grape::Entity
expose :connection_type
end

Rails collection_select passing id

I've read a bunch of questions but none of them are helping me with this problem. I am trying to create a form to make new forums but cannot get them to use the right category id.
<%= f.collection_select :category_id, Category.all, :id, :name %>
This creates a new forum but the id is not the category id from the drop down list. Here is the forums model
def new
#forum = Forum.new
end
def create
#forum = Forum.new(forum_params)
if #forum.save
redirect_to root_url
else
render 'new'
end
end
private
def forum_params
params.require(:forum).permit(:category_id, :name, :description )
end
end
Not quite sure what I am doing wrong here. Is it something to do with the foreign key? Any help would really be appreciated.
UPDATE
Forum Model
class Forum < ActiveRecord::Base
belongs_to :category
has_many :topics, dependent: :destroy
end
Category Model
class Category < ActiveRecord::Base
has_many :forums, dependent: :destroy
end
There is basically no category_id in the Forum model.
Here is a couple of things you can do to troubleshoot this. Run:
rails dbconsole
.schema
Check to see if you have category_id or not. If not, create a new migration for this.
Your initial code is correct. For some reason, I misread that your collection_select was already bound to the model.