updating the database after create action doesn't work - ruby-on-rails

set_bonus(member_id, cookie) method does not work. I'm trying to update the same model that that the self.set_signup_attribution(member_id, cookie, origin) returns.
The new_has_value variable returns {"currency"=>"usd", "type"=>"flat", "amount"=>1000}
Model.rb
# THIS METHOD WORKS
def self.set_signup_attribution(member_id, cookie, origin)
return unless cookie.present?
tracking_code = cookie
attribution_channel = AttributionChannel.find_by tracking_code: tracking_code
associated_member_record = Member.find member_id
if attribution_channel.present?
Attribution.create!({
event: Attribution::SIGN_UP,
attribution_channel: attribution_channel,
associated: associated_member_record,
extra: origin
})
set_bonus(member_id, cookie)
else
Rails.logger.info "Unknown Attribution Channel for tracking code: '#{ tracking_code }'"
end
end
# THIS METHOD DOES NOT WORK. UPDATES THE DATABASE.
def self.set_bonus(member_id, cookie)
epoch = Member.find_by(id: member_id).attribution_epoch
attribution_code = AttributionChannel.find_by(tracking_code: cookie)
duration_value = attribution_code.attribution_duration.downcase.split(' ')
duration = duration_value.first.to_i.send(duration_value.last)
return if cookie.present? && epoch.present?
current_time = Time.now
if attribution_code.bonus_config.present?
if (current_time - epoch).to_i < duration
hash_value = attribution_code.bonus_config
new_hash_value = hash_value.assoc("sign_up")[1]
value = Attribution.where(attribution_channel_id: attribution_code)
if new_hash_value["type"] == "flat"
value.update_all(
bonus_amount: new_hash_value["amount"],
bonus_currency: new_hash_value["currency"]
)
elsif new_hash_value["type"] == "percentage"
value.update_all(
bonus_amount: new_hash_value["amount"],
bonus_currency: new_hash_value["currency"]
)
else
{
bonus_amount: "Doesn't exist",
bonus_currency: "Doesn't exist"
}
end
else
"Do nothing"
end
else
"Do nothing"
end
#cookie = nil
binding.pry
end
Controller.rb
def index
unless session[:just_signed_up]
redirect_back_or_settings_page
end
Attribution.set_signup_attribution(current_user, cookies[:visit_attr], request.referer)
Attribution.set_bonus(current_user, cookies[:visit_attr])
session[:just_signed_up] = false
#email = current_user.email
end
How do I go about this? That is what I have tried and doesn't work. Can I merge set_bonus method to set_signup_attribution method or something?
Any help will be appreciated.
So drilling this further:
I merged set_bonus with set_signup_attribution and the two fields (bonus_amount and bonus_currency) which set_bonus method is supposed to update returns nil:
Attribution.create!(
{
event: Attribution::SIGN_UP,
attribution_channel: attribution_channel,
associated: associated_member_record,
extra: origin
}.merge(self.set_bonus(member_id, cookie).to_h)
)
With this drill after using binding.pry on that set_bonus method, I figured out it worked but it's returning nil and I don't know why. Could it be because member_id is not available in the model or something?

in your if statement you should call set_bonus method on appropriate object.
attribution = Attribution.create!({
event: Attribution::SIGN_UP,
attribution_channel: attribution_channel,
associated: associated_member_record,
extra: origin
})
attribution.set_bonus(member_id, cookie) if attribution.persisted?
Just be careful as .create! will raise an error in case there is something wrong, so maybe would be better to use
attribution = Attribution.new(.....)
if attribution.save
attribution.set_bonus(.....)
else
Rails.logger.info attribution.errors
end
I hope this would help.
Cheers

Related

SCIM userName in PATCH operation

I have implemented user provisioning/deprovisioning with SCIM like so :
users_controller.rb
class Scim::UsersController < Scim::ScimController
before_action :set_scim_provider
def index
startIndex = params[:startIndex].to_i
startIndex = 1 if startIndex < 1# if the user send a startIndex < 1, it is bad data, we don't take it.
itemsPerPage = params[:count].to_i
if itemsPerPage < 1 || itemsPerPage > #scim_provider.max_results
itemsPerPage = #scim_provider.default_number_of_results
end
scim_users = #scim_provider.identity_provider.communaute_accesses.from_scim
if params["filter"]
parser = Scim::QueryFilter::Parser.new
rpn_array = parser.parse(params["filter"])
tree = parser.tree
if tree.length == 3 and tree[0]== 'eq' and tree[1] == 'userName'
userName = tree[2]
scim_users = scim_users.where(provider_identifier: userName.delete('"'))
else
fail 'e'
end
end
paginated_users = scim_users.order(:created_at).offset(startIndex - 1).limit(itemsPerPage)
r = {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": scim_users.size,
"Resources": paginated_users.map { |ca| #scim_provider.representation_for_user(ca) },
"startIndex": startIndex,
"itemsPerPage": itemsPerPage
}
render_json_result(r, 200)
end
def create
if #scim_provider.identity_provider.communaute_accesses.from_scim.find_by(provider_identifier: #body_params['userName'])
render_409_conflict("uniqueness")
else
ca = #scim_provider.identity_provider.communaute_accesses.find_by(provider_identifier: #body_params['userName'], communaute_id: #scim_provider.identity_provider.communaute.id)
if ca.nil?
ca = #scim_provider.identity_provider.communaute_accesses.create(provider_identifier: #body_params['userName'], communaute_id: #scim_provider.identity_provider.communaute.id)
end
ca.update_last_raw_value("scim", #body_string)
ca.extract_values_from_scim
ca.queue_send
end
render_json_result(#scim_provider.representation_for_user(ca), 201)
end
def show
user = #scim_provider.identity_provider.communaute_accesses.from_scim.find_by(provider_identifier: #body_params['userName'])
if user
render_json_result(#scim_provider.representation_for_user(user), 200)
else
render_404_not_found(params[:id])
end
end
def update
ca = #scim_provider.identity_provider.communaute_accesses.from_scim.find_by(provider_identifier: #body_params['userName'])
uc = UserCommunaute.find_by(provider_identifier: #body_params['userName'])
ca.update_last_raw_value("scim", #body_string)
ca.extract_values_from_scim
unless ca.nil?
if ca.pending?
ca.update_last_raw_value("scim", #body_string)
ca.update(active: false)
if ca.active == false
fail "Unable to delete this user because of activeness" if ca.active == true
ca.destroy!
end
render_json_result(#scim_provider.representation_for_communaute_access_patch(ca), 200)
end
end
unless uc.nil?
uc.update(active: #body_params['active'])
if uc.active == false
uc.user.communaute_accesses.from_scim.destroy_all
uc.user.user_communautes.from_scim.destroy_all
render_json_result(#scim_provider.representation_for_user_communaute_patch(uc), 200)
end
end
end
end
Explanations:
When updating a user, SCIM sends a PATCH request like this:
{"schemas"=>["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations"=>[{"op"=>"Replace", "path"=>"active", "value"=>"False"}]} (#body_params in the code)
Which is what i am expecting. But, for a while, i was receiving the userName also in the body response during the PATCH operation.
This is how I fetch the correct user in my DB.
Actual result:
I don't receive the userName anymore when SCIM hits my update action.
Expected results:
Being able to receive information about the user during the PATCH operation to fetch the userName and find the right user in my database.
I have tried almost everything. When SCIM hits the index action, which it does everytime before going anywhere else, it does return me a userName et everything ends up as a 200 OK.
Then, when passing through update, it sends me nothing.
What I have tried last is to isolate the userName as an instance variable in the index action to fetch it after in the update like so:
# index
...
if params["filter"]
parser = Scim::QueryFilter::Parser.new
rpn_array = parser.parse(params["filter"])
tree = parser.tree
if tree.length == 3 and tree[0]== 'eq' and tree[1] == 'userName'
#user_name = tree[2]
scim_users = scim_users.where(provider_identifier: #user_name.delete('"'))
else
fail 'e'
end
end
...
# update
def update
ca = #scim_provider.identity_provider.communaute_accesses.from_scim.find_by(provider_identifier: #user_name)
uc = UserCommunaute.find_by(provider_identifier: #user_name)
ca.update_last_raw_value("scim", #body_string)
ca.extract_values_from_scim
...
But, #user_name in update seems to disappear as its value is nil.
I am deprovisioning from Azure Active Directory and Okta in a production environment.
Mapping is ok in both platforms.
Provisioning is working like a charm.
Please refer to https://developer.okta.com/docs/reference/scim/scim-20/#update-a-specific-user-patch for PATCH /Users/{userId}. Could you not make use of the userId in the url to identify the user ?

Stack level too deep on user.save

I want to assign a confirmation code to my users while creating one. And I also titleize some columns before saving-updating them. So my user.rb looks like this (it may be a bit messy):
// user.rb
*** some code ***
before_save { titleize_column(:name)
titleize_column(:surname)
capitalize_column(:complaints)
capitalize_column(:education)
capitalize_column(:job)
capitalize_column(:complaintsdetails)
capitalize_column(:prediagnosis)
capitalize_column(:existingdiagnosis)
capitalize_column(:knownilnessesother)
capitalize_column(:usedmedicine)
capitalize_column(:operation)
capitalize_column(:trauma)
capitalize_column(:allergy)
capitalize_column(:otherhabits)
capitalize_column(:motherother)
capitalize_column(:fatherother)
capitalize_column(:siblingsother)
}
before_save :generate_confirmation_code
protected
def generate_confirmation_code
unless self[:confirmed]
if(self[:type] == 'Patient')
update_attribute :confirmation_code, SecureRandom.urlsafe_base64(20)
update_attribute :confirmed, false
else
update_attribute :confirmed, true
end
end
end
protected
def capitalize_column(attr)
unless self[attr].nil?
self[attr] = Unicode::capitalize self[attr]
end
end
protected
def titleize_column(attr)
unless self[attr].nil?
words = self[attr].split
words.each_with_index do |v,i|
words[i] = Unicode::capitalize v
end
self[attr] = words.join(" ")
end
end
I'm using separate methods for titleizing and capitalizing columns because they may be nil when first creating a user, so I'm checking if it is null or not in those methods. This structure works fine on a normal signup with strong parameters. However, if I try to use twitter signup with the method below, it gives me the error 'stack level too deep' and I can see that it calls the generate_confirmation_code 123 times from the application trace and then these happens:
app/models/user.rb:83:in each'
app/models/user.rb:83:ineach_with_index'
app/models/user.rb:83:in titleize_column'
app/models/user.rb:20:inblock in '
app/models/user.rb:64:in generate_confirmation_code' (x123 times)
app/models/user.rb:101:infrom_omniauth'
app/controllers/socials_controller.rb:4:in `create'
// method for signing up/logging in a user from twitter
class << self
def from_omniauth(auth_hash)
if exists?(uid: auth_hash['uid'])
user = find_by(uid: auth_hash['uid'])
else
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'], type: 'Patient')
user.password_digest = User.digest('111111')
user.name = auth_hash['info']['name']
user.location = get_social_location_for user.provider, auth_hash['info']['location']
user.avatar = auth_hash['info']['image']
user.url = get_social_url_for user.provider, auth_hash['info']['urls']
user.save! // THIS IS THE LINE 101!
conversation = Conversation.create()
user.conversation = conversation
admin = Admin.first
admin.conversations << conversation
user.progress = Progress.create(active_state:1)
end
user
end
I think I'm messing up by using before_save not properly, but do not know how to do it right. What am I doing wrong here?
update_attribute also fires the save callbacks, thereby looping the before_save infinitely, thus producing stack level too deep.
You can just simply assign values in a before_save callback methods, because they will simply be saved afterwards anyway. See the following:
def generate_confirmation_code
unless self[:confirmed]
if(self[:type] == 'Patient')
self.confirmation_code = SecureRandom.urlsafe_base64(20)
self.confirmed = false
else
self.confirmed = true
end
end
end
You are calling update_attribute inside before_save callback method, instead you can just assign values to attributes. The method signature generate_confirmation_code should be like below -
def generate_confirmation_code
unless self[:confirmed]
if(self[:type] == 'Patient')
self.confirmation_code = SecureRandom.urlsafe_base64(20)
self.confirmed = false
else
self.confirmed = true
end
end
end

Issue with taking params

I grouped some Timesheet objects together using activerecord
scope :grouped_by_user_with_total_time, lambda {
group(:user_id, :day).select('user_id, SUM(time_worked) AS time_total, day, editable, approvable, accepted, comments')
}
And after I did that, now when I try to call the approve method on the grouped objects I get the error
Couldn't find TimeSheet without an ID
And it highlights the third line in my method
def approve
#time_sheets = []
*t = TimeSheet.find(params[:time_sheets])**
if t.instance_of?(Array)
#time_sheets = t
else
#time_sheets << t
end
successful = []
unsuccessful = []
#time_sheets.each do |timesheet|
unless timesheet.approved
timesheet.approve
if timesheet.save
successful << timesheet
else
unsuccessful << timesheet
end
end
end
I'm not very experienced with this and I am not sure how to check if my params[:time_sheets] is nil which might be the issue. Any help is appreciated.
Yes params[:time_sheets] being passed as nil would cause this error.
def approve
if params[:time_sheets]
#time_sheets = []
*t = TimeSheet.find(params[:time_sheets])**
if t.instance_of?(Array)
#time_sheets = t
else
#time_sheets << t
end
successful = []
unsuccessful = []
#time_sheets.each do |timesheet|
unless timesheet.approved
timesheet.approve
if timesheet.save
successful << timesheet
else
unsuccessful << timesheet
end
end
end
end
By adding a check with if params[:time_sheets] we can ensure the method only runs if the params[:time_sheets] is not nil. However it seems that you might be having a problem in passing your params. If you post your code where you show where you pass the params we could find out why it's passing nil.

inject method to retrieve data from hash

I'm having trouble getting the method below in my user model to handle a hash ('auth') I'm getting from LinkedIn for user signin:
def self.deep_get auth, *fields
auth.inject(auth) { |acc, e| acc[e] if acc }
end
I call the 'deep_get' method later in my user model as I create a user using omniauth/linkedin gem. However, it's returning nil values for the provider/uid/headline/email user fields that I know are not nil.
I included first_name and last_name fields as an example because this approach is working (not returning nil values), but (as I realize) bad style/exception handling. Any ideas as to why my deep_get inject method isn't working to retrieve the data in the hash as I'd like it to?
def self.create_from_omniauth(auth)
create! do |user|
# i'd like to retrieve user information from linkedin per the following with my inject method, but i am getting nil values when i should be getting data.
# :provider and :uid are on the same branch level of data. first_name,last_name,email,etc. are on a branch just below called 'info'
user.provider = deep_get(auth, :provider)
user.uid = deep_get(auth, :uid)
user.headline = deep_get(auth, :info, :headline)
user.email = deep_get(auth, :info, :email)
# the below is working but i know pokemon exception handling is not good style.
begin
user.first_name = auth["info"]["first_name"]
rescue
end
begin
user.last_name = auth["info"]["last_name"]
rescue
end
try this
def deep_find(obj,key)
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=deep_find(a.last,key) }
r
end
end
or try this
class Hash
def deep_fetch(key, default = nil)
default = yield if block_given?
(deep_find(key) or default) or nil
end
def deep_find(key)
if key?(key)
self[key]
else
self.values.inject(nil) do |memo, v|
memo = v.deep_find(key) if v.respond_to?(:deep_find)
memo unless memo.nil?
end
end
end
end

Rails server hangs when a User method I created is called. No errors and I don't know how to test it

I call this method (with helper method detailed below as well), defined in User.rb model
def get_random_items
return nil unless groups_as_member
if groups_as_member == 1
assignments = groups_as_member.assignments.limit(5)
random_items = Post.rand_by_post(assignments)
end
random_groups = groups_as_member.sort_by{rand}.slice(0,5)
random_items = Array.new
i=0
return unless random_groups
until i == 10 do
random_groups.each do |group|
assignments = group.assignments.limit(5)
if y = Post.rand_by_post(assignments)
random_items << y
i+=1
if random_items == 5
return random_items
end
else
return random_items
end
end
end
return random_items
end
helper method rand_by_post in Post.rb
def self.rand_by_post(assignments)
find_by_id(assignments.rand.post_id)
end
in the user controller show action:
def show
#public_groups = Group.public
#groups_member = #user.groups_as_member
#groups_as_owner = #user.groups_as_owner
#random_items = #user.get_random_items
end
when I comment out the call in the user show action, the user show works fine on my development and production server. But when I try to user the method the servers will just hang there, not doing anything. I can't find any errors in the server logs or the heroku logs. My test writing skills are pretty limited, and I am having trouble writing one for the entire method.
Can anyone spot a problem?
if your random_groups is empty, your helper method get_random_items will go into an endless loop until i == 10 do ... end. That could be the reason.
You might want to change return unless random_groups to return if random_groups.empty?

Resources