match two or more object from differents models with mongoid - ruby-on-rails

I have 2 model:
class User
include Mongoid::Document
field :email, :type => String, :null => false, :default => ""
.
.
end
class Admin
include Mongoid::Document
field :email, :type => String, :null => false, :default => ""
.
.
end
I want with a mongoid query find all users have a equal email in Admin model, something like:
User.where(:email => {exist_admin_class?})
This is possible? Or I have make a relationship between two model with a has_one User and belongs_to Admin
What is the best way to do this?
Thank you very much!

Indeed, MongoDB doesnt support cross collection queries. But it isnt necessary, especially not in this requirement. I would suggest using inheritance for that:
mongoid HowTo
Why: Just because admins are a special kind of users.

Related

How to map one user to another (from same table)

I'd like to know how to solve this problem in my model/migrations, with correct referential integrity/uniqueness constraints.
I have a user table with two types of user: support_worker and service_user (like teacher and pupil). A support_worker can provide support for many service_users. I used to have separate tables for these respective user types, but for simplicity it makes more sense to have both user types in a single 'user' table (for Devise).
I'll have another table called support_allocation which records the relationship between a support_worker and the service_user(s) they support - this support_allocation has other information stored about it (like a budget; time/money). So this table needs to map one user_id to another user_id. I imagine the table structure will look something like this: SupportAllocation (id, support_worker_id, service_user_id)
So far, my migrations look like this (I've used Devise gem to create the user table so this amends it):
class ChangeUsers < ActiveRecord::Migration[5.2]
def change
change_table :users do |t|
t.string :user_type # support_worker or service_user
t.string :given_name
t.string :family_name
t.string :customer_reference # only for service_users
t.date :date_of_birth # only for service_users
t.string :job_roles # only for support_workers
end
end
class CreateSupportAllocations < ActiveRecord::Migration[5.2]
def change
create_table :support_allocations do |t|
t.boolean :active, default: true
# This next bit is guesswork
t.integer support_worker_id # support_worker's user_id
t.integer service_user_id # service_user's user_id
t.timestamps
end
end
end
Here's where I get confused... I need to create a join, but this will only do it on user_id, whereas the relationship is defined by the two user_id columns (as shown and named above). I'm not sure if this a compound key or if a single foreign key (or two) will suffice.
Here's my migration work-in-progress:
class AddJoins < ActiveRecord::Migration[5.2]
def change
change_table :support_allocations do |t|
t.belongs_to :user, index: true
end
end
end
I'd like to know how to achieve this. For the record, I'm using ActiveAdmin for my app. Thank you for your help.
I don't think you need the AddJoins migration. Add 2 associations in your CreateSupportAllocations model like so:
belongs_to :support_worker, :foreign_key => :support_worker_id, :class_name => User
belongs_to :service_user, :foreign_key => :service_user_id, :class_name => User
In your activeadmin form you can set the collections for the select, for example
(in app/admin/support_allocations.rb)
form do |f|
f.inputs do
# your inputs
f.input :support_worker, :as => :select, :collection => User.where(:user_type => 'support_worker')
f.input :service_user, :as => :select, :collection => User.where(:user_type => 'service_user')
end
f.actions
end
# added after comments
index do
selectable_column
column :support_worker
actions
end
Add a to_s method in you user model like so:
def to_s
"#{self.full_name}"
end
Thanks for all your help. I added the suggested associations to my SupportAllocation model. For the record, I also had to add the following associations to my User model to make the join work fully, in both directions:
has_many :occurances_as_support_worker, :class_name => 'SupportAllocation', :foreign_key => 'support_worker_id'
has_many :occurances_as_service_user, :class_name => 'SupportAllocation', :foreign_key => 'service_user_id'
I used the example given here to work this out.
When accessing attributes specific to a type of user (i.e. using the join over support_worker_id OR service_user_id), on the index page. I use code like this:
column 'Service user', :full_name, :sortable => 'service_users.family_name' do |support_allocation|
#ServiceUser.find(support_allocation.service_user_id).full_name
support_allocation.service_user.full_name
end
column 'Support worker', :full_name, :sortable => 'support_workers.family_name' do |support_allocation|
support_allocation.support_worker.full_name
end

ActiveRecord has_one / has_many within same Model/table

I'm trying to figure out the best way to build my model. Each user can have many balances, but I would like to enforce one balance of each currency per user. The application controls the record generation, so perhaps this is overkill. However, the question perplexed me, so I thought I'd ask the community.
If it makes sense to do, what would be the best way to build this?
My migration thus far:
class CreateBalances < ActiveRecord::Migration
def change
create_table :balances do |t|
t.decimal :amount
t.integer :currency, default: 0, null: false # this will be an enum in the model
t.references :user, index: true
t.timestamps
end
end
end
TL;DR: one of each :currency per :user
Try this in your Balance model:
validates :currency, :uniqueness => true, :scope => user_id
What I think this says (and I could be wrong, so please take this with a grain of salt) is, "Make sure that this type of currency exists only once for this user." Failing that, you could also try a custom validation:
validates :unique_currency_balance_per_user
def unique_currency_balance_per_user
Balance.where('id != ?', id)
.where(:currency => currency, :user_id => user_id)
.present?
end
There are also likely to be database-level constraints, but I am not yet aware of these.

Mongoid create new Users

I'm trying to write an example app using Ruby on Rails and the Mongoid Mapper.
For some kind of Testing I want to write 1000 Testusers into MongoDB...
With the code bolow Mongoid is not able to write unique uid's. In my ruby console i got the right number for the counter but not for the uid.
Does anybody know what I forgot?
class User
include Mongoid::Document
include Mongoid::Timestamps
def self.create_users
(1..1000).each do |f|
user = User.new(uid: f.to_s, first_name: "first_name", last_name: "last_name", e_mail: "e_mail")
user.save!
puts f
puts user.uid
end
end
field :uid, :type => String
field :first_name, :type => String
field :last_name, :type => String
field :e_mail, :type => String
field :messages, :type => String
attr_accessible :first_name, :last_name, :e_mail
validates_presence_of :uid, :first_name, :last_name, :e_mail
validates_uniqueness_of :uid
has_many :messages
end
You don't have to provide the field uid in your models. MongoId add the id field for you and manages the value during the create operation.
Simply remove field :uid, :type => String from model
If you want to use your own ids you can change the name of the uid field to _id and it should work just fine. However, the default generated mongo _id will make it easier to scale and using it removes one of the more difficult aspects of sharding if you ever need that feature.
If you want to use the ones that are generated by default, they are included automatically unless overridden explicitly (behavior which you have seen) so just remove your custom field and you should be all set.
You can read more about ObjectIds here.

How do you validate uniqueness of a pair of ids in Ruby on Rails?

Suppose the following DB migration in Ruby:
create_table :question_votes do |t|
t.integer :user_id
t.integer :question_id
t.integer :vote
t.timestamps
end
Suppose further that I wish the rows in the DB contain unique (user_id, question_id) pairs. What is the right dust to put in the model to accomplish that?
validates_uniqueness_of :user_id, :question_id seems to simply make rows unique by user id, and unique by question id, instead of unique by the pair.
validates_uniqueness_of :user_id, :scope => [:question_id]
if you needed to include another column (or more), you can add that to the scope as well. Example:
validates_uniqueness_of :user_id, :scope => [:question_id, :some_third_column]
If using mysql, you can do it in the database using a unique index. It's something like:
add_index :question_votes, [:question_id, :user_id], :unique => true
This is going to raise an exception when you try to save a doubled-up combination of question_id/user_id, so you'll have to experiment and figure out which exception to catch and handle.
The best way is to use both, since rails isn't 100% reliable when uniqueness validation come thru.
You can use:
validates :user_id, uniqueness: { scope: :question_id }
and to be 100% on the safe side, add this validation on your db (MySQL ex)
add_index :question_votes, [:user_id, :question_id], unique: true
and then you can handle in your controller using:
rescue ActiveRecord::RecordNotUnique
So now you are 100% secure that you won't have a duplicated value :)
From RailsGuides. validates works too:
class QuestionVote < ActiveRecord::Base
validates :user_id, :uniqueness => { :scope => :question_id }
end
Except for writing your own validate method, the best you could do with validates_uniqueness_of is this:
validates_uniqueness_of :user_id, :scope => "question_id"
This will check that the user_id is unique within all rows with the same question_id as the record you are attempting to insert.
But that's not what you want.
I believe you're looking for the combination of :user_id and :question_id to be unique across the database.
In that case you need to do two things:
Write your own validate method.
Create a constraint in the database
because there's still a chance that
your app will process two records at
the same time.
When you are creating a new record, that doesn't work because the id of your parent model doesn't exist still at moment of validations.
This should to work for you.
class B < ActiveRecord::Base
has_many :ab
has_many :a, :through => :ab
end
class AB < ActiveRecord::Base
belongs_to :b
belongs_to :a
end
class A < ActiveRecord::Base
has_many :ab
has_many :b, :through => :ab
after_validation :validate_uniqueness_b
private
def validate_uniqueness_b
b_ids = ab.map(&:b_id)
unless b_ids.uniq.length.eql? b_ids.length
errors.add(:db, message: "no repeat b's")
end
end
end
In the above code I get all b_id of collection of parameters, then compare if the length between the unique values and obtained b_id are equals.
If are equals means that there are not repeat b_id.
Note: don't forget to add unique in your database's columns.

In a join table, what's the best workaround for Rails' absence of a composite key?

create_table :categories_posts, :id => false do |t|
t.column :category_id, :integer, :null => false
t.column :post_id, :integer, :null => false
end
I have a join table (as above) with columns that refer to a corresponding categories table and a posts table. I wanted to enforce a unique constraint on the composite key category_id, post_id in the categories_posts join table. But Rails does not support this (I believe).
To avoid the potential for duplicate rows in my data having the same combination of category_id and post_id, what's the best workaround for the absence of a composite key in Rails?
My assumptions here are:
The default auto-number column
(id:integer) would do nothing to
protect my data in this situation.
ActiveScaffold may provide a
solution but I'm not sure if
it's overkill to include it in my
project simply for this single
feature, especially if there is a
more elegant answer.
Add a unique index that includes both columns. That will prevent you from inserting a record that contains a duplicate category_id/post_id pair.
add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
It's very hard to recommend the "right" approach.
1) The pragmatic approach
Use validator and do not add unique composite index. This gives you nice messages in the UI and it just works.
class CategoryPost < ActiveRecord::Base
belongs_to :category
belongs_to :post
validates_uniqueness_of :category_id, :scope => :post_id, :message => "can only have one post assigned"
end
You can also add two separate indexes in your join tables to speed up searches:
add_index :categories_posts, :category_id
add_index :categories_posts, :post_id
Please note (according to the book Rails 3 Way) the validation is not foolproof because of a potential race condition between the SELECT and INSERT/UPDATE queries. It is recommended to use unique constraint if you must be absolutely sure there are no duplicate records.
2) The bulletproof approach
In this approach we want to put a constraint on the database level. So it means to create a composite index:
add_index :categories_posts, [ :category_id, :post_id ], :unique => true, :name => 'by_category_and_post'
Big advantage is a great database integrity, disadvantage is not much useful error reporting to the user. Please note in creating of composite index, order of columns is important.
If you put less selective columns as leading columns in index and put most selective columns at the end, other queries which have condition on non-leading index columns may also take advantage of INDEX SKIP SCAN. You may need to add one more index to get advantage of them, but this is highly database dependant.
3) Combination of both
One can read about combination of both, but I tend to like the number one only.
I think you can find easier to validate uniqueness of one of the fields with the other as a scope:
FROM THE API:
validates_uniqueness_of(*attr_names)
Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user can be named "davidhh".
class Person < ActiveRecord::Base
validates_uniqueness_of :user_name, :scope => :account_id
end
It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example, making sure that a teacher can only be on the schedule once per semester for a particular class.
class TeacherSchedule < ActiveRecord::Base
validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
end
When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
Configuration options:
* message - Specifies a custom error message (default is: "has already been taken")
* scope - One or more columns by which to limit the scope of the uniquness constraint.
* case_sensitive - Looks for an exact match. Ignored by non-text columns (true by default).
* allow_nil - If set to true, skips this validation if the attribute is null (default is: false)
* if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.
I implement both of the following when I have this issue in rails:
1) You should have a unique composite index declared at the database level to ensure that the dbms won't let a duplicate record get created.
2) To provide smoother error msgs than just the above, add a validation to the Rails model:
validates_each :category_id, :on => :create do |record, attr, value|
c = value; p = record.post_id
if c && p && # If no values, then that problem
# will be caught by another validator
CategoryPost.find_by_category_id_and_post_id(c, p)
record.errors.add :base, 'This post already has this category'
end
end
A solution can be to add both the index and validation in the model.
So in the migration you have:
add_index :categories_posts, [:category_id, :post_id], :unique => true
And in the model:
validates_uniqueness_of :category_id, :scope => [:category_id, :post_id]
validates_uniqueness_of :post_id, :scope => [:category_id, :post_id]

Resources