Rails 3- find_by not working - ruby

I have a strange problem.
I have two models Users and roles with a many to many relationships between them.
I want to filter the roles with name 'Administrator' within the role collection of a user.
In the model this code
puts self.roles.to_s
Prints to screen:
[Role id: 1, name: "Administrator", created_at: "2012-01-22 21:55:45", updated_at: "2012-01-22 21:55:45"]
But this code
puts self.roles.find_by_name('Administrator').to_s
doesn't print anything. And this one:
puts self.roles.find_by_name('Administrator').nil?
prints true!
Why isn't my find_by_name method not working?
I tryied it in the console and it works well.
My code snippet is the following:
puts self.roles.to_s
puts self.roles.find_by_name('Administrator').to_s
puts self.roles.find_by_name('Administrator').nil?
And the output is the following:
[Role id: 1, name: "Administrator", created_at: "2012-01-22 21:55:45", updated_at: "2012-01-22 21:55:45"]
<none>
true
What am I doing wrong?? It has to be something stupid.
This code is located in a validate method, so it is executed before the user is saved.

You mentioned that you are doing this in a validation before the model is saved. This is why it's failing. Since the User is not yet saved, it doesn't have an id in your database, so there's no way for it to search your associations.
The reason the first line works (self.roles.to_s) is that Rails memorizes any roles you add to your user, but does not save them to the database until you save the user. Once you save the user, your second/third lines would work fine:
user = User.new
user.roles << Role.first
user.roles
# => [#<Role id: 1, name: "Administrator" ...>]
user.roles.find_by_name("Administrator")
# => nil
user.save # This inserts both the new user AND the associations
user.roles.find_by_name("Administrator")
# => #<Role id: 1, name: "Administrator" ...>
If you have to work with this in your validations, you might try using Enumerable's find method to search the roles array instead:
user = User.new
user.roles << Role.first
user.roles
# => [#<Role id: 1, name: "Administrator" ...>]
user.roles.find { |role| role.name == "Administrator" }
# => #<Role id: 1, name: "Administrator" ...>
user.roles.find { |role| role.name == "Foo" }
# => nil

Related

Send additional information with Raven.send_event

I have integrated Sentry in my Ruby On Rails application and I want to send a custom event when a specific case happens, I am able to send the events with
Raven.send_event({:message => 'Custom event'})
How do i send additional information related to the same. I need to send for example, user_id, email_id, login_name and other custom parameters.
You can set user_context to raven using Raven.user_context method
You can write a method to set context in Application Controller and call the same in before action
For example
Raven.user_context(
# a unique ID which represents this user
id: current_user.id, # 1
# the actor's email address, if available
email: current_user.email, # "example#example.org"
# the actor's username, if available
username: current_user.username, # "foo"
# the actor's IP address, if available
ip_address: request.ip # '127.0.0.1'
)
You can write in application controller as
class ApplicationController < ActionController::Base
before_action :set_raven_context, if: proc { Rails.env.production? } //If you want to set only in prod
def set_raven_context
Raven.user_context(
# a unique ID which represents this user
id: current_user.id, # 1
# the actor's email address, if available
email: current_user.email, # "example#example.org"
# the actor's username, if available
username: current_user.username, # "foo"
# the actor's IP address, if available
ip_address: request.ip # '127.0.0.1'
)
#You can also set extra context using `Raven.extra_context`
Raven.extra_context app: url, environment: Rails.env, time: Time.now
end
end
Addition to #opensource-developer answer:
You can find more available extra parameters here => Sentry Usage Page
For anyone facing similar issue, i managed to do it via, under the key extra you can specify any extra keys which you want to send to sentry for further debugging
Raven.capture_message "custom message",
logger: 'logger',
extra: {
time_at: Time.now
},
tags: {
env: Rails.env
}

ActiveAdmin Collection Names

I have an array of roles in my model:
ROLES = ['super_admin', 'user', 'user_admin']
I have an ActiveAdmin form that shows these roles:
input :roles, as: :check_boxes, collection: User::ROLES
I have to show the roles as humanized, capitalized names instead of snake case on the form:
Super Admin, Salesman, Sales Admin
but when one of them is selected, it has to be saved in snakecase.
I've tried this:
User::ROLES
.map { |r| "#{r.humanize}" }
.map { |r| r.split.map(&:capitalize).join(' ') }
but this saves the role as the humanized, capitalized form instead of the snake case form. How can I use the humanized, capitalized version of the words on the form, but save the snake case version?
You can use Rails(Active Support) method String#titleize instead split.map(&:capitalize).join(' ')
If you want to store record in DB as snakecase you can do below way: In dropdown option it will be displayed as titalize but it's value will be set as snakecase which will be stored in DB.
> roles.map{|e|[e.titleize, e]}
#=> [["Super Admin", "super_admin"], ["User", "user"], ["User Admin", "user_admin"]]
in activeadmin use:
input :roles, as: :check_boxes, collection: User::ROLES.map{|e|[e.titleize, e]}
If you want to use pure Ruby, one alternative could be:
'super_admin'.split('_').map(&:capitalize).join(' ')
#=> "Super Admin"
So, mapping your array:
roles.map { |str| [str.split('_').map(&:capitalize).join(' '), str] }
#=> [["Super Admin", "super_admin"], ["User", "user"], ["User Admin", "user_admin"]]
Backward (before_save):
"Super Admin".downcase.gsub(' ', '_') #=> "super_admin"

Ruby query need to update

Relationship:
account has_many users
user has_one sima
primary_partner_id is "account_id" which is passing as params.
User.where(primary_partner_id: 2).map{|a| a.sima}.reject{ |e| e.to_s.empty?}
Results as below:
[
#<Sima id: 93, user_id: 7, interviewer_account_user_id: 1945, interviewer_completion_date: "2017-06-09", transcriber_account_user_id: nil, transcriber_completion_date: nil, biographer_account_user_id: nil, biographer_completion_date: nil, reviewer_account_user_id: nil, reviewer_completion_date: nil, status: "accepted", autobiographical_form: "27381", autobiographical_form_completion_date: nil, sima_level_id: "1", created_at: "2017-06-06 20:17:57", updated_at: "2017-06-09 10:04:33", autobiographical_form_comments: nil, on_hold: nil, comments: [{:comment=>"easylims.xlsx", :user_name=>"Mike Burns", :created_at=>2017-06-06 20:17:57 UTC}, {:comment=>"ok", :user_name=>"SIMA Admin", :created_at=>2017-06-06 20:19:33 UTC}], interviewer_id: nil, interviewer_start_date: nil, transcriber_start_date: nil, biographer_start_date: nil, reviewer_start_date: nil>,
#<Sima id: 92, user_id: 1, interviewer_account_user_id: nil, interviewer_completion_date: nil, transcriber_account_user_id: nil, transcriber_completion_date: nil, biographer_account_user_id: nil, biographer_completion_date: nil, reviewer_account_user_id: nil, reviewer_completion_date: nil, status: "accepted", autobiographical_form: "27437", autobiographical_form_completion_date: nil, sima_level_id: "1", created_at: "2017-06-06 20:01:50", updated_at: "2017-06-06 20:22:50", autobiographical_form_comments: nil, on_hold: nil, comments: [{:comment=>"original_msg (1).txt", :user_name=>"bild_cloud#bild.org", :created_at=>2017-06-06 20:01:50 UTC}, {:comment=>"ok", :user_name=>"SIMA Admin", :created_at=>2017-06-06 20:22:05 UTC}], interviewer_id: nil, interviewer_start_date: nil, transcriber_start_date: nil, biographer_start_date: nil, reviewer_start_date: nil>
]
Sima has field like status and I have three statuses as "pending, accepted, declined"
Question:
Now I want to show the result as per status which will pass by the user as params. So the result should be as per status from Sima. If params is status=" accepted" then Sima list will have only which has status "accepted"
By doing this you can avoid N+1 Query problem
User.includes(:sima).where(sima: {status: params[:status]},primary_partner_id: params[:account_id]).map(&:sima)
If you are using joins --> In map statement each time query will be called so that we are preferred to use includes, for more Information
you can refer this link
http://guides.rubyonrails.org/active_record_querying.html
You could join the simas model and the user model
User.joins("JOIN simas ON simas.user_id = users.id")
.where(primary_partner_id: 2, "simas.status": 'accepted')
.map{|a| a.sima}
As I am understanding your above all statements. End user us passing account id and you want to take sima status from the end user as well.
You can file all users id from account first. then find sima which will belong to that user and here you can pass sima status as well.
#aBadAssCowboy given the correct answer which is first one only. But from that query, you will get all Sima either nil or with value.
user_ids = User.where(primary_partner_id: params[:account_id]).pluck(:id)
Sima.where(user_id: user_ids, status: params[:status]).reject{ |e| e.to_s.empty?}
Hope this will help you.
User.preload(:sima).joins(:sima).where(sima: {status: params[:status]}, primary_partner_id: params[:account_id]).map(&:sima)
or
As account to users relationship is already defined you can directly do...
Account.find(2).users.preload(:sima).joins(:sima).where(sima: {status: params[:status]}).map(&:sima)
With the same associations you have on the models, you could do (assuming that user passes status they wants to see:
user_ids = User.where(primary_partner_id: 2).pluck(:id)
Sima.where(user_id: user_ids, status: params[:status])
The above will hit the database twice but that's totally fine. You could use includes and map but I don't think that will be a huge performance benefit depending on the number of users on a account you have. Eitherways, here it is:
User.includes(:sima)
.references(:sima) # you need this if you use Rails 4 +
.where(users: { primary_partner_id: 2 })
.where(simas: { status: params[:status] })
.map(&:sima)
However, I suggest you update Account model with has_many through associations.
class Account
has_many :users
has_many :simas, through: :users
end
# To get sima of a particular status
Account.find(2).simas.where(status: param[:status])
This way, you can just do account.simas to get simas from all the users that belong to that account.
If you add to Sima Model
belongs_to :user You can get an list of Sima by
Sima.joins(:users).where(users: { primary_partner_id: 2 }).where(status: 'accepted')

Adding element to postgres array field fails while replacing the whole array works

I have an User model which has an array of roles.
From my schema.db:
create_table "users", force: true do |t|
t.string "roles", array: true
My model looks like this:
class User < ActiveRecord::Base
ROLES = %w(superadmin sysadmin secretary)
validate :allowed_roles
after_initialize :initialize_roles, if: :new_record?
private
def allowed_roles
roles.each do |role|
errors.add(:roles, :invalid) unless ROLES.include?(role)
end
end
def initialize_roles
write_attribute(:roles, []) if read_attribute(:roles).blank?
end
Problem is when I try to add another role from console like user.roles << "new_role" then user.save! says true and asking user.roles gives me my wanted output. But when I ask User.find(user_id).roles then I get the previous state without "new_role" in it.
For ex.
user.roles
=> ["superadmin"]
user.roles << "secretary"
=> ["superadmin", "secretary"]
user.save!
=> true
user.roles
=> ["superadmin", "secretary"]
User.find(<user_id>).roles
=> ["superadmin"]
When replacing the whole array, it works as I want:
user.roles
=> ["superadmin"]
user.roles = ["superadmin", "secretary"]
user.save!
=> true
user.roles
=> ["superadmin", "secretary"]
User.find(<user_id>).roles
=> ["superadmin", "secretary"]
I'm using rails 4 and postgresql, roles are for cancancan gem.
Changing other fields like user.name for ex works like expected. I made quite a lot of digging in google, but no help.
Active Record tracks which columns have changed and only saves these to the database. This change tracking works by hooking onto the setter methods - mutating an object inplace isn't detected. For example
user.roles << "superuser"
wouldn't be detected as a change.
There are 2 ways around this. One is never to change any Active Record object attribute in place. In your case this would mean the slight clumsier
user.roles += ["superuser"]
If you can't/won't do this then you must tell Active Record what you have done, for example
user.roles.gsub!(...)
user.roles_will_change!
lets Active Record know that the roles attribute has changed and needs to be updated.
It would be nicer if Active Record dealt better with this - when change tracking came in array columns weren't supported (mysql had the lion's share of the attention at the time)
Yet another approach would be to mark such columns as always needing saving (much like what happens with serialised attributes) but you'd need to monkey patch activerecord for that.
Frederick's answer got me thingking and I wrote a simple gem deep_dirty that provides deep dirty checking by comparing current attribute values to those recast from *_before_type_cast. To automate this on ActiveRecord models, the gem sets up a before_validation callback.
Usage
gem 'deep_dirty'
class User < ActiveRecord::Base
include DeepDirty
end
user.roles << 'secretary'
user.changed? # => false
user.valid? # => true
user.changed? # => true
Also, deep checking can be initiated without validations:
user.changed? # => false
user.deep_changed? # => true
user.changed? # => true
Check out the source code at github: borgand/deep_dirty

search an array and do a specific task based on if a value is found

Ok let me ask this another way. This is an example of an inciident table I'm going to import in a csv format.
id:
incident_datetime: 2012-09-24 08:07:00.000000+0000
location: Hamburg
report_nr: 33-11-00201
responsible_party: John
area_resident: No
street: 6108 FoxHunt
city: Lexington
state: Ky
home_phone: 111-111-1111
cell_phone: 222-222-2222
insurance_carrier_name: Nationwide
insurance_carrier_street: Waller
insurance_carrier_city: Lexington
insurance_carrier_state: KY
insurance_carrier_phone: 666-666-6666
insurance_carrier_contact: Jason
policy_nr: 2sdf2sdf
vin_nr: ZTZHK3fhr5546107407
license_nr: 520-VDH
vehicle_make: Toyota
vehicle_model: Tacoma
vehicle_year: 2004
created_at:
updated_at:
status: Recieved
user_id: 16
resp_zip: 40509
insur_zip: 40509
And here is the code for the rakefile.
require 'csv'
namespace :import_incidents_csv do
task :create_incidents => :environment do
puts "Import Incidents"
csv_text = File.read('c:/rails/thumb/costrecovery_csv/lib/csv_import/incidents.csv')
csv = CSV.parse(csv_text, :headers => true)
#incident_id_array = []
csv.each do |row|
row = row.to_hash.with_indifferent_access
Incident.create!(row.to_hash.symbolize_keys)
#incident_id_array << Incident.last.id
end
end
end
The id at the top will automatically get assigned a value when it gets dumped into the database. I'm then taking that id value and saving it in an array and creating a timesheet for it. This is what the timesheet looks like.
id:
name:
date:
incident_id: x
created_at:
updated_at:
The timesheet needs to be created because a bunch of models "belong to it". And here is the rakefile to create this:
require 'csv'
namespace :import_timesheets_csv do
task :create_timesheets => :environment do
puts "Import Timesheets"
csv_text = File.read('c:/rails/thumb/costrecovery_csv/lib/csv_import/timesheets.csv')
csv = CSV.parse(csv_text, :headers => true)
#timesheet_id_array = []
csv.each_with_index do |row,index|
row = row.to_hash.with_indifferent_access
Timesheet.create!(row.to_hash.symbolize_keys)
#timesheet_id_array << Timesheet.last.id
timesheet = Timesheet.last
timesheet.incident_id = #incident_id_array[index]
timesheet.save
end
end
end
So at this point, I've created a new incident, and it's linked to a new timesheet I just created. Now I want to import a csv file creating safety_officers. Here is what the file would look like.
id:
name: Safety Officer 1
first_hour: 1
additional_hours: 2
total_hours: 3
hazmat_hours: 0.5
rate1:
rate2:
timesheet_id:
created_at:
updated_at:
And here is the code for the Importing the safety_officer.csv file.
require 'csv'
namespace :import_safety_officers_csv do
task :create_safety_officers => :environment do
puts "Import Safety Officers"
csv_text = File.read('c:/rails/thumb/costrecovery_csv/lib/csv_import/safety_officers.csv')
csv = CSV.parse(csv_text, :headers => true)
csv.each_with_index do |row,index|
row = row.to_hash.with_indifferent_access
SafetyOfficer.create!(row.to_hash.symbolize_keys)
safety_officer = SafetyOfficer.last
safety_officer.timesheet_id = #timesheet_id_array[index]
safety_officer.save
end
end
end
So above is an example of 1 incident report being imported, 1 timesheet being imported/created and 1 safety officer being imported. I'm trying to figure out how my code needs to be modified so the safety officers know which incident report they belong to. How do I assign them the proper timesheet_id that corresponds to their incident. I have several other models that I have to do this with as well but i just need to get it figured out for one of them and then I can duplicate it for the rest. And all of these are saved as different rake files and are executed by one master rake task which looks like this.
require 'csv'
namespace :combine_csv do
task :combine => :environment do
Rake::Task["import_incidents_csv:create_incidents"].invoke
Rake::Task["import_timesheets_csv:create_timesheets"].invoke
Rake::Task["import_safety_officers_csv:create_safety_officers"].invoke
end
end

Resources