Rails 4 create associations in class method - ruby-on-rails

This works perfectly fine:
User.first.social_profiles.create!
On the other hand, this creates the social_profile but does not create the association between the two:
class SocialProfile < ActiveRecord::Base
def self.create_google( auth_info )
# if where(provider: auth_info["provider"], uid: auth_info["uid"]).empty?
create! do |google|
google.provider = auth_info["provider"]
google.uid = auth_info["uid"]
google.image_url = auth_info["info"]["image"]
google.email = auth_info["info"]["email"]
google.access_key = auth_info["credentials"]["token"]
google.refresh_token = auth_info["credentials"]["refresh_token"]
google.expires_at = Time.at(auth_info["credentials"]["expires_at"])
google.expires = auth_info["credentials"]["expires"]
end
# else
# where(provider: auth_info[:provider], uid: auth_info[:uid]).first
# end
end
end
Console:
2.1.2 :102 > User.first.social_profiles.create_google( ...the auth hash ...)
What's the problem here? How can I fix it?
This does work though
p = User.first.social_profiles.create_google(...the auth hash ...)
User.first.social_profiles << p

The User.first instance does not get carried into the SocialProfile.create_google method, and therefore the create! method wouldn't have the user instance available.
You can assign it yourself by passing it in:
class SocialProfile < ActiveRecord::Base
def self.create_google( user, auth_info )
create! do |google|
google.user_id = user.id,
...
end
end
end
And calling it with
SocialProfile.create_google( User.first, auth_info )
Alternatively, consider having the create_google_profile method in User, so that you can
class User < ActiveRecord::Base
def create_google_profile( auth_info )
self.social_profiles.create(
provider: auth_info["provider"],
...
)
end
end
and calling it with
User.first.create_google_profile( auth_info )

Related

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

ruby on rails accessing custom class attributes from its object

I have a custom class in my application controller. Like below:
class Defaults
def initialize
#value_1 = "1234"
#value_2 = nil
#data = Data.new
end
end
class Data
def initialize
#data_1 = nil
end
end
Now in my controller method i have created an object of type Defaults
def updateDefaultValues
defaults = Defaults.new
# i am unable to update the value, it says undefined method
defaults.value_2 = Table.maximum("price")
defaults.data.data_1 = defaults.value_2 * 0.3
end
How to access value_2 from defaults object?
defaults.value_2
Also, how to access data_1 attribute from data object within defaults object?
defaults.data.data_1
You should use attr_accessor:
class Defaults
attr_accessor :value_1, :value_2, :data
# ...
end
defaults = Defaults.new
defaults.value_1 = 1
# => 1
defaults.value_1
# => 1
As you are using def as a keyword to define the method, that means def is a reserved keyword. You can't use reserved keywords as a variable.
You just need to rename your variable name from def to something_else and it should work! Your code will look like this:
def updateDefaultValues
obj = Defaults.new
obj.value_2 = Table.maximum("price")
obj.data.data_1
end
EDIT:
As per OP's comment & updated question, he had used def just as an example, here is the updated answer:
You may need attr_accessor to make attrs accessible:
class Defaults
attr_accessor :value_1, :value_2, :data
...
...
end
class Data
attr_accessor :data_1
...
...
end
Add value_2 method in Defaults class
class Defaults
def initialize
#value_1 = "1234"
#value_2 = nil
#data = Data.new
end
def value_2
#value_2
end
end
class Data
def initialize
#data_1 = nil
end
end

Initializing a class not connected to other classes -- Ruby on Rails

I'm trying to initialize a singular class in my RoR application. This Batch class is not connected to any other class, it is used solely for the Rails API I have set up.
This is the Batch class:
class Batch < ActiveRecord::Base
before_create :access_bucket
def access_bucket
s3 = AWS::S3.new
bucket = s3.buckets['curateanalytics']
bucket.objects.each do |obj|
if obj =~ /swipe batches/i && obj =~ /jpg/i
self.sort_objs(obj.key)
end
end
end
def sort_objs(url)
swipe = url.split("swipe batches/").last
batch_id = url.split("swipe batches/")[1]
folder = swipe.split("/")[0]
self.initialize(batch_id, folder, url)
end
def initialize()
batch = Batch.new
batch.batch_id = batch_id
batch.folder = folder
batch.url = url
batch.save!
end
end
I honestly had no idea where to go so I created a before_create :create_batch method in my User class:
class User < ActiveRecord::Base
has_one :like
has_one :outfit
has_one :wardrobe
before_create :create_batch
after_create :create_wardrobe, :create_outfit, :create_like
serialize :preferences
def self.from_omniauth(auth_hash)
where(auth_hash.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.curate_user_id = "curate"+rand(9).to_s+rand(9).to_s+rand(9).to_s+
rand(9).to_s+rand(9).to_s
user.provider = auth_hash.provider
user.uid = auth_hash.uid
user.name = auth_hash.info.name
user.email = auth_hash.info.email
user.image = auth_hash.info.image
user.oauth_token = auth_hash.credentials.token
user.oauth_expires_at = Time.at(auth_hash.credentials.expires_at)
user.preferences = { height: nil, weight: nil, age: nil, waist_size: nil, inseam: nil, preferred_pants_fit: nil, shirt_size: nil, preferred_shirt_fit: nil, shoe_size: nil}
user.save!
end
end
private
def create_batch
#batch = Batch.new
#batch.save!
end
end
When I ran the server I received the message that the stack was too deep. Am I wrong in thinking that this path should access the Batch class and the Batch.access_bucket method which would then lead to the initialize method?
Delete initialize method in the Batch class.
When you call new on a Class, it instantiates an object and call initialize on it. So when you call Batch.new in create_batch method of your User class, the initialize method of you Batch class is called. The problem is that Batch#initialize method calls Batch.new inside it, so another Batch#initialize is invoked, which calls Batch.new inside it, which again calls another Batch#initialize, and infinite cycle of Bathc.new and Batch#initialize follows.

Undefined method 'any?' for nil:NilClass

I'm trying to create a validator that validates whether a user typed in a clean word. I'm using the Obscenity gem but created some of my own methods to ensure quality data.
class MyValidator < ActiveModel::Validator
#mystery_words = # This is a mystery, I can't tell you.
#mystery_c = #mystery_words.map(&:capitalize)
#mystery_u = #mystery_words.map(&:upcase)
#mysteries = #mystery_words + #mystery_c + #mystery_u
#new_mysteries = #mysteries.map{|mystery|mystery.tr("A-Za-z", "N-ZA-Mn-za-m")}
def validate (user)
if Obscenity.profane?(user.name) \
|| #new_mysteries.any?{|mystery|user.name.include?(mystery)} \
|| #new_mysteries.any?{|mystery|user.email.include?(mystery)} \
|| #new_mysteries.any?{|mystery|user.password.include?(mystery)}
user.errors[:name] << 'Error: Please select a different username'
end
end
end
class User < ActiveRecord::Base
include ActiveModel::Validations
validates_with MyValidator
Error Message
NoMethodError: undefined method any? for nil:NilClass
I don't understand why I received that message.
You have defined #new_mysteries on the class, rather than on the instance.
Consider:
class MyValidator
#new_mysteries = ['a','b','c']
def validate
p #new_mysteries
end
end
MyValidator.new.validate
# => nil
Inside the validate method, # variables refer to variables on the instance (an instance created by new). Outside of a method definition, # variables refer to instances of the class itself. Hence the disconnect.
I would probably declare #new_mysteries inside the constructor:
class MyValidator
def initialize
#new_mysteries = ['a','b','c']
end
def validate
p #new_mysteries
end
end
MyValidator.new.validate
# => ["a", "b", "c"]
Further reading: http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
There is a issue of variable scope in your code. Try following:
class MyValidator < ActiveModel::Validator
def mystery_setup
#mystery_words = # This is a mystery, I can't tell you.
#mystery_c = #mystery_words.map(&:capitalize)
#mystery_u = #mystery_words.map(&:upcase)
#mysteries = #mystery_words + #mystery_c + #mystery_u
#new_mysteries = #mysteries.map { |mystery| mystery.tr("A-Za-z", "N-ZA-Mn-za-m") }
end
def validate(user)
mystery_setup
if Obscenity.profane?(user.name) || #new_mysteries.any?{|mystery|user.name.include?(mystery)} || #new_mysteries.any?{|mystery|user.email.include?(mystery)} \
|| #new_mysteries.any?{|mystery|user.password.include?(mystery)}
user.errors[:name] << 'Error: Please select a different username'
end
end
end

Rails Active Record Instance Variables

My questions is in regards to this AR and its instance variable #saved
class PhoneNumber < ActiveRecord::Base
has_one :user
validates_presence_of :number
def self.create_phone_number( user, phone_hash )
#new_phone = PhoneNumber.new(phone_hash)
#user = user
PhoneNumber.transaction do
#user.phone_numbers << #new_phone
#new_phone.save!
#user.save!
end
#saved = true
return #new_phone
rescue ActiveRecord::RecordInvalid => invalid
#saved = false
return #new_phone
end
def saved?
#saved ||= false
end
end
It is my understanding that the instance variables will keep their values through the existence of the instance.
When using this AR in my controller, saved? always returns false..
#phone_number = PhoneNumber.create_phone_number(#active_user, params[:phone_number])
puts "add_phone_number"
if #phone_number.saved? => always false
What am I missing in regards to these instance variables? Thank you
you're using the instance variable #saved inside a class method, the #saved var then belongs to the class, not to its instances, so you cannot call it from an instance method like #saved?.
what you can do, is, at the top of the class add:
attr_accessor :saved
and inside the create_phone_number method, replace:
#saved = true
with:
#new_phone.saved = true
then it should work

Resources