In the User model I have an archive! method that is called when a User is destroyed. This action creates a new ArchivedUser in separate table.
The ArchivedUser is successfully created, but the way I am manually setting each value is pretty dirty; if a new column is added to the User table it must be added here as well.
I tried to select and slice the attributes, but got undefined local variable or methoduser'`
ArchivedUser.create(user.attributes.select{ |key, _| ArchivedUser.attribute_names.include? key })
ArchivedUser.create(user.attributes.slice(ArchivedUser.attribute_names))
How can I iterate through each attribute in the User table when creating an ArchivedUser with self?
def archive!
if ArchivedUser.create(
user_id: self.id,
company_id: self.company_id,
first_name: self.first_name,
last_name: self.last_name,
email: self.email,
encrypted_password: self.encrypted_password,
password_salt: self.password_salt,
session_token: self.session_token,
perishable_token: self.perishable_token,
role: self.role,
score: self.score,
created_at: self.created_at,
updated_at: self.updated_at,
api_key: self.api_key,
device_id: self.device_id,
time_zone: self.time_zone,
device_type: self.device_type,
verified_at: self.verified_at,
verification_key: self.verification_key,
uninstalled: self.uninstalled,
device_details: self.device_details,
is_archived: self.is_archived,
registered_at: self.registered_at,
logged_in_at: self.logged_in_at,
state: self.state,
creation_state: self.creation_state,
language_id: self.language_id,
offer_count: self.offer_count,
expired_device_id: self.expired_device_id,
unique_id: self.unique_id,
best_language_code: self.best_language_code,
offer_id: self.offer_id,
vetted_state: self.vetted_state,
photo_path: self.photo_path
)
self.is_archived = true
self.email = "#{self.email}.archived#{Time.now.to_i}"
self.encrypted_password = nil
self.password_salt = nil
self.session_token = nil
self.perishable_token = nil
self.device_id = nil
self.verification_key = nil
self.save!
self.update_column(:api_key, nil)
UserGroup.delete_all(:user_id => self.id)
else
# handle the ArchivedUser not being created properly
end
end
Thanks for viewing :)
Update:
We were able to figure out the reasons why ArchivedUser.create(self.attributes.slice!(ArchivedUser.attribute_names) wasn't working. The first reason is the create method requires "bang" to write the object. The second reason was that ArchivedUser has a user_id field, that User doesn't receive until after create. We have to set the user_id: manually with merge(user_id: self.id)
The final output looks like
ArchivedUser.create!(self.attributes.slice!(ArchivedUser.attribute_names).merge(user_id: self.id))
You were on the right track with the first implementation. You just have to use self instead of user.
ArchivedUser.create(self.attributes.slice(ArchivedUser.attribute_names))
If you just want to have a copy of the user that is being archived, I think an elegant way would be to do
archived_user = user_to_be_archived.dup
or you can take a look at the amoeba gem, this will do all the heavy lifting including associations if you want.
https://github.com/amoeba-rb/amoeba
Related
I'm initializing a new object and setting the attributes (because there are no attributes for this particular object) before rendering a form like so:
def new
Book.new title: nil, author: nil, genre: nil, language: nil, ect...
end
This to me looks like a code smell.
I'm trying to set the attributes in a method within the model so I can increase readability by using: Book.new.set_attributes. So my set_attributes method in the Book model would look like:
def set_attributes
{posted: nil, company: nil, poster: nil, city: nil, state: nil, title: nil, body: nil, keywords: nil}
end
However this does not work (with or without the {} brackets). Is it possible to call a method after using .new?
Ruby's constructor method is initialize, not new. You shouldn't try to define a method called new. Do something like:
class Book
attr_accessor :title, :author
def initialize(title = nil, author = nil)
#title = title
#author = author
end
end
You don't need to initialize nil values. When calling Book.new, any values that are not provided in a hash (e.g., Book.new(title: 'Bozo', author: 'Clown')) will be nil automatically.
The main thing I am looking to achieve from this question is understanding. With some assistance I have been looking at refactoring my controller code into more manageable modules/classes so that I can test them effectively. I have an example here that I would like to work on, my question is how would I test the class Sale:
class TransactionsController < ApplicationController
def create
payment = BraintreeTransaction::VerifyPayment.new(params, #user_id, #transaction_total)
payment.run(params)
if payment.success?
redirect_to thank_you_path
else
flash.now[:alert] = payment.error
flash.keep
redirect_to new_transaction_path
end
end
module BraintreeTransaction
class VerifyPayment
def initialize(params, user_id, total)
#transaction_total = total
#user_id = user_id
#params = params
#error_message = nil
end
def run(params)
#result = BraintreeTransaction::Sale.new.braintree_hash(params, #transaction_total)
if #result.success?
#cart_items = CartItem.where(user_id: #user_id).where.not(image_id: nil)
#cart_items.destroy_all
create_real_user
update_completed_transaction
guest_user.destroy
#success = true
else
update_transaction
#error_message = BraintreeErrors::Errors.new.error_message(#result)
end
end
def success?
#success
end
def error
#error_message
end
end
module BraintreeTransaction
class Sale
def braintree_hash(params, total)
Braintree::Transaction.sale(
amount: total,
payment_method_nonce: params[:payment_method_nonce],
device_data: params[:device_data],
customer: {
first_name: params[:first_name],
last_name: params[:last_name],
email: params[:email],
phone: params[:phone]
},
billing: {
first_name: params[:first_name],
last_name: params[:last_name],
company: params[:company],
street_address: params[:street_address],
locality: params[:locality],
region: params[:region],
postal_code: params[:postal_code]
},
shipping: {
first_name: params[:shipping_first_name].presence || params[:first_name].presence,
last_name: params[:shipping_last_name].presence || params[:last_name].presence,
company: params[:shipping_company].presence || params[:company].presence,
street_address: params[:shipping_street_address].presence || params[:street_address].presence,
locality: params[:shipping_locality].presence || params[:locality].presence,
region: params[:shipping_region].presence || params[:region].presence,
postal_code: params[:shipping_postal_code].presence || params[:postal_code].presence
},
options: {
submit_for_settlement: true,
store_in_vault_on_success: true
}
)
end
end
end
I don't know if I am looking at this wrong but this piece of code here BraintreeTransaction::Sale.new.braintree_hash is what I want to test and I want to ensure that when called the class receives a hash ?
Update
So far I have come up with this (though I am not 100% confident it is the correct approach ?)
require 'rails_helper'
RSpec.describe BraintreeTransaction::Sale do
#transaction_total = 100
let(:params) { FactoryGirl.attributes_for(:braintree_transaction, amount: #transaction_total) }
it 'recieves a hash when creating a payment' do
expect_any_instance_of(BraintreeTransaction::Sale).to receive(:braintree_hash).with(params, #transaction_total).and_return(true)
end
end
I get an error returned which I don't understand
Failure/Error: DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
Exactly one instance should have received the following message(s) but didn't: braintree_hash
I might not be spot on but I would answer the way I would have tackled the issue. There are three ways you can write a test that hits the code you want to test.
Write a unit test for braintree_hash for BraintreeTransaction::Sale object
Write a controller unit method for create method in TransactionsController controller
write an integration test for route for create method in TransactionsController.
These are the ways you can start exploring.
A couple of things here. All the suggestions for refactoring your code (from your other question Writing valuable controller tests - Rspec) apply here. I can make further suggestions on this code, if helpful.
In your test, I believe your problem is that you never actually call BraintreeTransaction.new.braintree_hash(params) (which should be called immediately following your expect_any_instance_of declaration). And so no instances ever receive the message(s).
I am making requests to the Facebook API and some of the responses are empty/nil and I am wondering how I can delete these so that when I save them to my model I don't have any nil entries.
def formatted_data
for record in results['data'] do
attrs = {
message: record['message'],
picture: record['picture'],
link: record['link'],
object_id: record['object_id'],
description: record['description'],
created_time: record['created_time']
}
attrs.delete_if { |x| x.nil? }
Post.where(attrs).first_or_create! do |post|
post.attributes = attrs
end
end
As you can see I am trying to use the delete_if method but it's not working.
Here's an example of a response that I would like to delete:
id: 45
message:
picture:
link:
object_id:
large_image_url:
description:
created_time: 2014-04-12 11:38:02.000000000 Z
created_at: 2014-05-01 10:27:00.000000000 Z
updated_at: 2014-05-01 10:27:00.000000000 Z
This kind of record is no good to me as it has no message, so maybe I could make the query specify if message.nil ? then delete
Edit
Been reading the delete_if docs and after iceman's comment, I thought this would work but it doesn't, though it seems closer to what I want:
attrs = attrs.delete_if {|key, value| key = 'message', value = nil }
There are about 25 records returned, of which 5 should be deleted, but after running the above I get one result left in the model:
[#<Post id: 81, message: nil, picture: nil, link: nil, object_id: nil, large_image_url: nil, description: nil, created_time: nil, created_at: "2014-05-01 11:22:40", updated_at: "2014-05-01 11:22:40">]
Why are all the rest being deleted, maybe my syntax for accessing the key is incorrect?
Since #delete_if passes into block two arguments: the key, and value, try this usage:
attrs.delete_if { |k,v| v.nil? }
and for ruby-on-rails you can remove all blank lines, i.e. nil, and empty:
attrs.delete_if { |k,v| v.blank? }
Im adding this in that someone could provide a more efficient way of doing this, maybe before the records get written to the model..But i have managed a solution, albeit a hacky one i would say
I have added this after the creation of the posts
delete_if_nil = Post.where(message: nil)
delete_if_nil.destroy_all
Its another query on the db which isnt ideal i guess
Any other suggestions appreciated
In the code below, the returned user object is not nil, but not all members have their relevant values e.g.
User id: 18, provider: nil, uid: nil, name: "newonenewone", oauth_token: nil, oauth_expires_at: nil, created_at: "2013-12-28
22:17:35", updated_at: "2013-12-28 22:17:35", email:
"newonenewone#newonenewone.com", encrypted_password:
"14972b4..."
But when checking if the user object is nil it returned true! So why this happens and how to fix it.
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
Please walk through your code in the case when the user is not nil but the user does not have a password. In that case, the authenticate method will return nil. This happens because you are not handling any cases after the second return statement. This results in a nil return by Ruby convention.
In other words, you may want to add code below:
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
# TODO: handle case when !user.nil? && !user.has_password?
end
I'm probably misunderstanding something here.
I have a model Secondant that I create with Fabrication in my model rspec.
main_user = Fabricate :user, email: TESTEMAIL
sec = Fabricate :secondant, email: SECEMAIL, user_id: main_user.id
sec_user = Fabricate :user, email: SECEMAIL
ActionMailer::Base.deliveries = []
debugger
At this point when I look at the value of secondant_id in the sec model, the attribute is empty (it get's filled in a after_create callback). When i retrieve the model just created from the database that attribute is filled. Why are those two not in sync?
27 main_user = Fabricate :user, email: TESTEMAIL
28 sec = Fabricate :secondant, email: SECEMAIL, user_id: main_user.id
29 sec_user = Fabricate :user, email: SECEMAIL
30 ActionMailer::Base.deliveries = []
31 debugger
=> 32 sec.destroy
33 end
34
35 it 'should have a secondant_id assigned' do
36 sec.secondant_id.should_not be_nil
(rdb:1) e sec
#<Secondant id: 519, user_id: 1095, email: "secondant#hotmail.com", secondant_id: nil, created_at: "2013-10-10 13:13:29", updated_at: "2013-10-10 13:13:29", reported: false>
(rdb:1) e Secondant.where(id: sec.id).first
#<Secondant id: 519, user_id: 1095, email: "secondant#hotmail.com", secondant_id: 1096, created_at: "2013-10-10 13:13:29", updated_at: "2013-10-10 13:13:29", reported: false>
My after_create callback:
def find_user
user = User.where(email: self.email).first
if user
# create the link to the user
self.secondant_id = user.id
self.save
# see if this is the second one
if Secondant.where('user_id = ? and secondant_id is not null', user_id).count == 2
user.do_somthing
end
end
return
end
EDIT
There is a similar callback in the user class, which is firing in this case (thanks Peter)
def find_secondant
Secondant.where(email: email).find_each do |sec|
sec.secondant_id = id
sec.save
end
end
At the time you create sec, the user with the identical email has not been created, so your after_save callback should not be setting secondant_id.
I can only assume that your find_user method is getting invoked as a result of the User creation or the where operation you are executing in the debugger, resulting in the secondant_id field being set at that time. It won't be reflected in sec unless/until you do a reload, as the Ruby object created by where is distinct from the sec Ruby object.