Rails Active Record Instance Variables - ruby-on-rails

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

Related

Why is my service not changing my instance variable?

My service is supposed to match the current user with an opponent. I have a method in my service that is supposed to find this opponent but it's not saving it in my controller. I realize this is a very basic question but I'm very new to rails, thank you!
Here is my service:
class MatchUser
attr_accessor :user_params
def initialize(user_params)
#user_params = user_params
end
def match(opponent)
return false if user_params[:matched] == true
#unmatched_users = User.where(matched: false)
#unique_unmatched_users = #unmatched_users.where.not(id: user_params[:id])
#same_league_unmatched_users = #unique_unmatched_users.where(league_id:
user_params[:league_id])
return false if #same_league_unmatched_users.empty?
opponent = #same_league_unmatched_users.sample
opponent.faceoff_date = Time.now
user_params[:faceoff_date] = Time.now
opponent.save!
end
end
Here is the part in my controller where I'm getting an error when I try to assign #matched_user_team because #matched_user is nil
#user = current_user
#matching = MatchUser.new(#user)
if #matching.match(#matched_user)
#matched_user_team = #matched_user.teams.last.chars
end

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

Access varible in ruby after initialize

I am trying to access variable in ruby after initialize, but i didn't get that variable , anything wrong in that?
class Test
def initialize(params)
#has_test = params[:has_test]
#limit_test = params[:limit_test]
end
def self.method1(params)
Test.new(params)
#can i get that two instance variable
end
end
You should probably set up attribute accessors, then use them this way:
class Test
attr_accessor :has_test
attr_accessor :limit_test
def initialize(params)
#has_test = params[:has_test]
#limit_test = params[:limit_test]
end
def self.method1(params)
t = Test.new(params)
// can i get that two instance variable
// Yes:
// use t.has_test and t.limit_test
end
end
You are mixing an instance and a class method in your example.
If this is really what you want, then you have to define an accessor with attr_reader:
class Test
def initialize(params)
#has_test = params[:has_test]
#limit_test = params[:limit_test]
end
attr_reader :has_test
attr_reader :limit_test
def self.method1(params)
obj = Test.new(params)
p obj.has_test
p obj.limit_test
end
end
Test.method1(has_test: 1, limit_test: 3)
It the instance/class-method is a mistake, then this example may help you:
class Test
def initialize(params)
#has_test = params[:has_test]
#limit_test = params[:limit_test]
end
def method1()
p #has_test
p #limit_test
end
end
obj = Test.new(has_test: 1, limit_test: 3)
obj.method1
If you define also the accessors like in the first code, then you have again access from outside the class.
Just in case you don't want a reader, see also Access instance variable from outside the class

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

Rails: ActiveRecord interdependent attributes setters

In activerecord, attribute setters seems to be called in order of the param hash.
Therefore, in the following sample, "par_prio" will be empty in "par1" setter.
class MyModel < ActiveRecord::Base
def par1=(value)
Rails.logger.info("second param: #{self.par_prio}")
super(value)
end
end
MyModel.new({ :par1 => 'bla', :par_prio => 'bouh' })
Is there any way to simply define an order on attributes in the model ?
NOTE: I have a solution, but not "generic", by overriding the initialize method on "MyModel":
def initialize(attributes = {}, options = {})
if attributes[:par_prio]
value = attributes.delete(:par_prio)
attributes = { :par_prio => value }.merge(attributes)
end
super(attributes, options)
end
Moreover, it does not works if par_prio is another model that has a relation on, and is used to build MyModel:
class ParPrio < ActiveRecord::Base
has_many my_models
end
par_prio = ParPrio.create
par_prio.my_models.build(:par1 => 'blah')
The par_prio param will not be available in the initialize override.
Override assign_attributes on the specific model where you need the assignments to happen in a specific order:
attr_accessor :first_attr # Attr that needs to be assigned first
attr_accessor :second_attr # Attr that needs to be assigned second
def assign_attributes(new_attributes, options = {})
sorted_new_attributes = new_attributes.with_indifferent_access
if sorted_new_attributes.has_key?(:second_attr)
first_attr_val = sorted_new_attributes.delete :first_attr
raise ArgumentError.new('YourModel#assign_attributes :: second_attr assigned without first_attr') unless first_attr_val.present?
new_attributes = Hash[:first_attr, first_attr_val].merge(sorted_new_attributes)
end
super(new_attributes, options = {})
end

Resources