Mongoid: ActiveModel Numericality Validation, allow_nil does not work - validation

I've defined a Mongoid model with an Integer field for which i validate numericality like this
# source.rb
class Source
field :code, type: Integer
validates_numericality_of :code, allow_nil: true
The purpose of allow_nil is to validate fields which are present & ignore nil values.
But here, allow_nil completely bypasses the numericality check
object = Source.new
object.code = "ABC"
object.valid?
=> true
object
=> #<Source _id: 50d00b2d81ee9eae46000001, _type: nil, code: 0>
In activerecord, this works correctly
object = Source.new
object.code = "ABC"
object.valid?
=> false
object
=> #<Source id: nil, code: 0, created_at: nil, updated_at: nil>
object.save
(0.1ms) begin transaction
(0.1ms) rollback transaction
=> false

Mongoid behaves slightly different to Active Record when using #valid? on already persisted data. Active Record's #valid? will run all validations whereas Mongoid's #valid? will only run validations on fields where data has changed as an optimization. - see mongoid validation
so this could be your problem.
you could try
validates_numericality_of :code, :allow_nil => true
and
validates :code, :numericality => true ,:allow_nil => true

Related

Why is validation being skipped?

I have an email ActiveRecord (sub-class, different PG DB) with the following validation:
class Email < DbRecord
belongs_to :user
attr_accessor :skip_validation
alias_method :skip_validation?, :skip_validation
validates :value,
presence: true,
uniqueness: true,
format: {
with: URI::MailTo::EMAIL_REGEXP,
message: "is an invalid email address",
allow_blank: true,
},
unless: :skip_validation?
before_validation { |record| record.value = record.value&.downcase }
skip_validation is nil. There are no other instance methods.
When there's no user_id the validation works as expected.
> e = Email.new(value: "foo#bar")
=> #<Email id: nil, user_id: nil, value: "foo#bar">
> e.valid?
=> false
When there's a user_id the bogus email doesn't trigger the validation.
> e = Email.new(user_id: 7, value: "foo#bar")
=> #<Email id: nil, user_id: 7, value: "foo#bar">
> e.valid?
=> true
Note that setting validate: true on belongs_to doesn't help:
class Email < DbRecord
belongs_to :user, validate: true
Still presents:
> e = Email.new(user_id: 7, value: "foo#bar")
=> #<Email id: nil, user_id: 7, value: "foo#bar">
> e.valid?
=> true
Why is that? What else should I be looking at/for?
Two-step answer:
"foo#bar" is a valid email according to URI::MailTo::EMAIL_REGEXP...
I can't even, but others have hit the same issue so...
As I always tell everyone else: check your assumptions. I assumed the validation was failing because of the email address, and in my sleep-deprived state I didn't verify the errors.
So why was it failing validation?
Rails 5 changed belongs_to to make the related ID mandatory, so in order for this to make sense (in my use-case) I also needed to add:
belongs_to :user, optional: true
to get the expected error messages back during validation.

Create a Model Scope using hash attribute

I am using mongoid-history gem and mongoid in Ruby. I am actually linking the mongo history to a model called SocialPost, so i can make something like.
history = current_user.social_posts.history_tracks
Now i need to filter this 'history' with a scope or a method that filter attribute 'association_chain' from history tracker model, but 'history_tracks' attributes are made in this way:
<HistoryTracker _id: 57bdc1cb65e59325ae000001, created_at: 2016-08-24 15:48:27 UTC, updated_at: 2016-08-24 15:48:27 UTC, association_chain: [{"name"=>"SocialPost", "id"=>BSON::ObjectId('57ac8b0f65e5930944000000')}], modified: {"facebook_likes_count"=>2594213, "tweeter_followers_count"=>0}, original: {}, version: 1, action: "update", scope: "social_post", modifier_id: nil>
So, how i can create a search in my HistoryTracker model that allow me to search a specific group of ids inside association_chain, something like this:
HistoryTracker.where(:association_chain.in => {"name"=>"SocialPost", "id"=>[GROUPS OF IDS TO SEARCH]}
UPDATE using $elemMatch
#test case multiple social_id: empty result
HistoryTracker.any_in({:association_chain.elem_match => [{name: 'SocialPost', :id.in => social_ids}] })
#test case 1 social_id: match result
HistoryTracker.any_in({:association_chain.elem_match => [{name: 'SocialPost', :id => social_ids.first}] })
I found already a posible solution. The elemMatch should be constructed grouped with ids you need to collect, so this is what i did:
social_ids = [BSON::ObjectId('...1'), BSON::ObjectId('...2'), BSON::ObjectId('...3')]
#reorder the ids with the extra hash elements
a = []
social_ids.each do |id_bson|
a << {name: 'SocialPost', id: id_bson}
end
And now you create the 'QUERY'
HistoryTracker.any_in({:association_chain.elem_match => a})

How to stop DataMapper from double query when limiting columns/fields?

I'm not sure if I'm at fault here or if my approach is wrong with this.
I want to fetch a user (limiting columns/fields only to name, email, id):
#user = User.first(:api_key => request.env["HTTP_API_KEY"], :fields => [:id, :name, :email])
The output in the command line is correct as follows:
SELECT "id", "name", "email" FROM "users" WHERE "api_key" = '90e20c4838ba3e1772ace705c2f51d4146656cc5' ORDER BY "id" LIMIT 1
Directly after the above query, I have this code:
render_json({
:success => true,
:code => 200,
:user => #user
})
render_json() looks like this, nothing special:
def render_json(p)
status p[:code] if p.has_key?(:code)
p.to_json
end
The problem at this point is that the #user variable contains the full user object (all other fields included) and DataMapper has made an additional query to the database to fetch the fields not included in the :fields constraint, from the logs:
SELECT "id", "password", "api_key", "premium", "timezone", "verified", "notify_me", "company", "updated_at" FROM "users" WHERE "id" = 1 ORDER BY "id"
My question is this: how do I stop DM from performing the additional query? I know it has to do with it's lazy loading architecture and that returning the #user variable in JSON assumes that I want the whole user object. I particularly don't want the password field to be visible in any output representation of the user object.
The same behaviour can be seen when using DM's own serialisation module.
I think you should use an intermediate object for json rendering.
First, query the user from database :
db_user = User.first(:api_key => request.env["HTTP_API_KEY"], :fields => [:id, :name, :email])
Then, create a "json object" to manipulate this user :
#user = { id: db_user.id, name: db_user.name, email: db_user.email }

How to test Rspec For controller,

How to Rectify this error in RSpec for Controller,
1) SellersController GET index find the Activity
Failure/Error: assigns(:activity).should eq([activity])
expected: [#<Activity id: 65, transactable_type: "admin", transactable_id: 1, action_type: "seller", user_id: 1, is_approved: false, approved_by: nil, created_at: "2012-04-09 11:02:17", updated_at: "2012-04-09 11:02:17", associatable_type: nil, associatable_id: nil>]
got: nil
(compared using ==)
Seller_rspec.rb
describe "GET index" do
it "find the Activity" do
activity = Activity.create!(:transactable_type=>"admin",:transactable_id=>1,:action_type=>"seller",:user_id =>1,:is_approved=>0)
get :index,{:is_approved => activity.to_param,:user_id=>1,:approved_by=>"admin"}
assigns(:activity).should eq([activity])
end
In controller
def index
#activities=Activity.find(:all,:select => 'DISTINCT transactable_type,transactable_id,action_type,is_approved,approved_by',:conditions=>["is_approved= ? and user_id=? and approved_by IS NULL",false,current_user.id])
end
You are putting a code into controller which should go to the model. Create a method or scope in Activity model like:
def self.find_not_approved(current_user_id)
find(:all,
:select => 'DISTINCT transactable_type,transactable_id,action_type,is_approved,approved_by',
:conditions= ["is_approved= ? and user_id=? and approved_by IS NULL",
false,
current_user_id])
end
So you can just have in controller (I've made up the method name):
def index
#activities = Activity.find_not_appoved(current_user.id)
end
And just to anser your question, it should be assigns(:activities).should eq([activity]) not assigns(:activity).should eq([activity]) - as your are checking #activities variable in controller not, #activity.

Attribute Not Being Added to Object

I'm trying to add an attribute to a model object. Direct access works but when I print the entire object or encode it into JSON, that attribute is left out. Can someone tell me what I'm doing wrong?
Here is my rails console output:
irb(main):010:0> b=ChatMessage.new(:user_id=>4,:room_id=>1,:message=>"Hello World")
=> #<ChatMessage id: nil, room_id: 1, user_id: 4, message: "Hello World", created_at: nil, updated_at: nil>
irb(main):011:0> b.sender_nickname="bbb"
=> "bbb"
irb(main):012:0> b.sender_nickname
=> "bbb"
irb(main):013:0> b
=> #<ChatMessage id: nil, room_id: 1, user_id: 4, message: "Hello World", created_at: nil, updated_at: nil>
Here is my model code:
class ChatMessage < ActiveRecord::Base
attr_accessor :sender_nickname
def self.get_last_message_id
last_message=ChatMessage.all.last
last_message.nil? ? 0 : last_message.id
end
def self.get_all_messages_after(room_id,message_id)
ChatMessage.where("room_id = ? AND id > ?",room_id,message_id)
end
end
edit:
Here is the migration file for chat_messages table.
I'm not really looking to save sender_nickname. So it's more like a virtual attribute (but is still in db through association). And I might need to add other attributes later that aren't in the db. Is it possible to do it without using association?
def self.up
create_table :chat_messages do |t|
t.integer :room_id
t.integer :user_id
t.string :message
t.timestamps
end
end
as far as I know to_json will only take the attributes in the model and serialize (as in chat_message.attributes, not attr_accessor).
You properbly got a sender, or user model, or anything like that.
What I would do is to make a relation to the sender, user or what its called, with a belong_to, and then use this code to convert it to json:
chat_message.to_json(:include => { :sender => { :only => :nickname } })
It may also work with you code, and then just:
chat_message.to_json(:include => { :sender_nickname })
There also some documentation here: http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
Hope it helps :)

Resources