Ruby_on_rails session and syntax - ruby-on-rails

I'm new to rails. Looking at code (for a filter on data) someone else has written. Trying to figure out what exactly the following mean:
session[:filters] - just a hash with the chosen filters from the session? is :filters a session option or defined in the code somewhere?
session[:filters][controller] - same as 1) but specifying the controller?
session[:filters][controller][session[:user]] -??
session[:filters][controller].delete(session[:user]) -??

Basically session is a Hash object used by rails to store data as a cookie on client-side, and, as a Hash, it uses keys to store information (more information on hashes here).
In your code, :filters is just a key of the session hash (you can tell because of the :), and it contains information regarding, i assume, filters. So :filters is not defined anywhere else, although it should be set somewhere else, with something like:
session[:filters] = some_filter
In the second line you see [:filters][controller], that means that :filters is also a Hash, and as such it also contains keys, one of them being the name of the controller (again, i assume it is because of the name, but i could be wrong). Here controller is just a variable (notice there is no :) which contains the name of a key for the :filters hash.
So, if you are following along, you can tell now that whatever controller value is, is another Hash. In line 3 session[:user] is a key for that hash (where :user is another key of the session hash, which i assume holds the user name or id).
Finally, in line 4 delete deletes the key session[:user] from the hash (i.e. removes it from the cookie).
Consider this example:
# create session hash with :user key
session = { :user => "user79" }
#=> {:user=>"user79"}
# create another hash with session[:user] key and any value
hash1 = { session[:user] => "my value" }
#=> {"user79"=>"my value"}
# create controller variable with a value
controller = "users"
#=> "users"
# create another hash with controller key and hash1 as a value
hash2 = { controller => hash1 }
#=> {"users"=>{"user79"=>"my value"}}
# add :filters key to session with hash2 as a value
session[:filters] = hash2
# Retrieve the value from the session hash
session[:filters][controller][session[:user]]
#=> "my value"
# Delete session[:user] key from the session hash
session[:filters][controller].delete(session[:user])
#=> "my value" (delete returns the deleted value)
# Retrieve the value from the session hash
session[:filters][controller][session[:user]]
#=> nil
session[:filters][controller]
#=> {} (returns empty hash since we deleted the only key it had)

Related

Ruby on Rails: How can i iterate over params and convert any empty strings into nil values in a controller?

Rails newbie here.
I have an integration with stripe where users can update the billing address on their card, however, stripe doesn't accept empty strings, only nil values, and it's possible that users won't need to fill in the second address line for example.
How would I go about iterating through params received from a form and convert empty strings into nil?
I have a Stripe Tool module that handles stripe related tasks.
In my controller i have:
def add_billing_address
account_id = current_user.account_id
account = Account.find_by(id: account_id)
stripe_id = account.stripe_customer_id
# convert params empty strings to nil here
StripeTool.add_billing_address(stripe_id: stripe_id,
stripe_token: params[:stripeToken],
address_line1: params[:address_line1],
address_line2: params[:address_line2],
address_city: params[:address_city],
address_state: params[:address_state],
address_zip: params[:address_zip]
)
# redirects and error handling happens after this
You can call .map .each on the params hash in the controller like this:
params.each do |key, value|
params[key] = nil if value === ''
end
But it's probably better to let your form return a nil value when a field contains no data.
I would recommend to avoid modifying the values in the params object, cause it is not good practice to change them in place. It is better to create a new object the has the values you want to use.
stripe_params = params.select { |_,v| v.present? }
This will create a new object without any of the blank attributes. I'm guessing that if an attribute is nil, you might as well not pass it at all.

Use merge! or deep_merge! to update hash in activerecord model

I have a JSON column in my model that by default is an empty hash.
I want to check if specific keys are present and if not to merge the empty hash with a default hash with the keys.
In my model I am checking if a utility (water, gas, or electric is present) and if not then insert this default hash:
def default_config
{:config => {"features" => {"utilities" => {"water" => true, "gas" => true, "electric" => true}}}}
end
this is how I'm checking for whether a utility key is present:
def water
has_water? || parent.has_water?
end
which in turn calls these methods (all in my model):
def utility(util)
self[:config].try(:fetch, "features", nil).try(:fetch, "utilities", nil).try(:fetch, "#{util}", nil)
end
def has_water?
utility("water") == true
end
This is in order to be able to configure the JSON column whether or not the keys already present, which I'm attempting here:
def set_water(boolean)
new_val = cleaned_boolean(boolean)
water ? nil : self[:config].deep_merge!(default_config)
self[:config]["features"]["utilities"]["water"] = new_val
end
When I test this I'm getting
undefined method `[]=' for nil:NilClass
error when trying to set a utility value indicating that my default_config is not being merged into the existing empty hash.
reverse_merge! is the usual way to set defaults for a Hash in rails.
self[:config].reverse_merge!(default_config)
this is essentially equal to:
default_config.merge!(self[:config])
Leaving everything in self[:config] untouched and just merging in the missing key value pairs from default_config.
Also this ternary expression:
water ? nil : self[:config].deep_merge!(default_config)
is more idiomatically written as (using Hash#reverse_merge!)
self[:config].reverse_merge!(default_config) unless water
and since water returns a boolean value it is generally written as a question e.g. water? (like in has_water?) Not sure if the water method is used frequently but I would refactor as
def has_water?(include_parent=false)
utility("water") == true || (include_parent && parent.has_water?)
end
Then call as:
self[:config].reverse_merge!(default_config) unless has_water?(true)

How to lazy load constants into rails environment

I have a set of API keys that I want to load into the rails environment, for easy and frequent access (without hitting the database). How can I lazy load it:
1) Let's say there is an API_KEY hash in rails environment (that I initialize using an initializer)
2) When I look up for a key, I first look up in API_KEY hash, if not found, I fetch from database, and at the same time add to API_KEY hash, such that it is accessible for all future requests.
3) In case someone changes the the api key, I can update API_KEY hash in case the key exists.
Can someone help me with the calls to create, delete and update the API_KEY hash in the rails environment (from within the code, instead of the initial rails loading)? Will there be a problem in this approach if each passenger thread loads the rails environment separately?
Any other problems that you see with this approach? The data set (number of api keys) is finite.
Since you're actually just storing values in a Hash (its being assigned to a constant is immaterial since you're not freezing it or anything) I would use the block form of Hash.new. I don't know what your database looks like, but supposing you had these values stored in a model called APIKey that has attributes name and value:
API_KEY = Hash.new do |hash, key_name|
hash[key_name] = APIKey.where(:name => key_name).pluck(:value)
end
Now when you access the API_KEY hash with a key that doesn't exist, it will query the APIKey model and assign the value of the value attribute to that element of the hash. Suppose you have this in your api_keys table:
name value
--------- ----------------
S3_ACCESS 0123456789abcdef
Then you could access the hash defined above like this:
puts API_KEY[:S3_ACCESS]
# Query: SELECT `value` FROM `api_keys` WHERE `name` = 'S3_ACCESS']
# => 0123456789abcdef
puts API_KEY.inspect
# => { :S3_ACCESS => "0123456789abcdef" }
puts API_KEY[:S3_ACCESS]
# No query!
# => 0123456789abcdef
If you want to update the value at runtime then you can do it like any hash:
API_KEY[:S3_ACCESS] = '5555555555ffffff'
# => "5555555555ffffff"
puts API_KEY.inspect
# => { :S3_ACCESS => "5555555555ffffff" }
However, changing the hash will not update the database record.
Advanced
If you want the database record to be updated if you update the hash, you'll have to override Hash#[]=, and if you're going to go that far you might as well use ActiveRecord directly. For example:
class APIKey < ActiveRecord::Base
attr_accessible :name, :value
##_cached_values = Hash.new do |_cached_values, key_name|
# This will return nil if there's no record in the database with the
# given name; alternatively you could use `first!` which would raise a
# RecordNotFound exception which you could rescue and do something
# useful with.
_cached_values[key_name] = self.where(:name => key_name).pluck(:value)
end
def self.[](key_name)
##_cached_values[key_name]
end
def self.[]=(key_name, new_value)
# If the database already has a value for this key_name, fetch the object;
# otherwise initialize a new object
api_key = self.where(:name => key_name).first_or_initialize
# Update the value and save the record
api_key.update_attributes!(:value => new_value)
# Update the cached value
##_cached_values[key_name] = new_value
end
end
puts APIKey[:S3_ACCESS]
# Query: SELECT `value` FROM `api_keys` WHERE `name` = 'S3_ACCESS'
# => 0123456789abcdef
APIKey[:S3_ACCESS] = '5555555555ffffff'
# Query: UPDATE `api_keys` SET `value` = '5555555555ffffff'
# WHERE `name` = 'S3_ACCESS'
# => '5555555555ffffff'
APIKey[:NEW_KEY] = 'new_val'
# Query: INSERT INTO `api_keys` (`name`, `value`)
# VALUES ('NEW_KEY', 'new_val')
# => 'new_val'
With this kind of implementation APIKey[:S3_ACCESS] would work the same as the API_KEY example above; and APIKey[:S3_ACCESS] = 'foo' would perform an UPDATE or INSERT as necessary.
This is probably reinventing the wheel, though; at this point you're probably better off using one of the many configuration management gems people much smarter than me have written. Also, let us not forget The Twelve-Factor App, which exhorts us to store config in the environment.
P.S. You get ridiculous overimplementation points if you define APIKey.const_missing so you can do APIKey::S3_ACCESS instead of APIKey[:S3_ACCESS].

For a hash of objects in ruby, hash.keys.index(obj) works but hash.has_key?(obj) does not. Why?

Here's my Rails class
class SkinnyEmployee
include ActiveModel::Validations
attr_accessor :uid, :name
validates :uid, :presence => true
def initialize(id, name)
#uid = id
#name = name
end
def ==(other)
puts "Calling =="
raise ArgumentError.new("other is nil or bad in "+self.to_s) if other.nil? or !other.instance_of?(SkinnyEmployee)
return (self.class == other.class && self.uid == other.uid)
end
alias :eql? :==
end
I have a hash of SkinnyEmployee objects. E.g.,
skinny_hash = {SkinnyEmployee.new("123", "xyz") => 1, SkinnyEmployee.new("456", "abc") => 2}
I have another SkinnyEmployee object that I want to look up. E.g.,
entry = SkinnyEmployee.new("456", "abc")
When I do
skinny_hash.keys.index(entry)
I get 1, as expected. But when I do
skinny_hash.has_key?(entry)
I get false.
Why is that? Doesn't has_key? also use == or eql? to find whether a key exists in a hash?
Thanks much for the help!
First, this drove me nuts. What you're doing looked absolutely correct to me, and, as you already know, doesn't work.
I can take you part of the way to a solution:
http://ruby-doc.org/core-2.0.0/Object.html#method-i-hash
quoting:
Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.
The hash value is used along with eql? by the Hash class to determine if two objects reference the same hash key. Any hash value that exceeds the capacity of a Fixnum will be truncated before being used.
I added:
def hash
1
end
to your SkinnyEmployee Class, and has_key? started returning true. Obviously that's not a solution, but I'm thinking it at least puts you on the path to one.
You have overwritten the eql? method used by Array#index but not the hash method used by Hash#has_key?.
From Ruby docs for Object#hash
Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.
The Object#hash and Object#eql? methods return equal if and only if the objects occupy the same space in memory. Some classes like Array overwrite both methods to return true if the compared array's have same elements.
For your case you can define the hash method like:
def hash
"#{self.class}_#{self.uid}".hash
end
This would satisfy the docs criteria for hash method given above.
That is happening because the object you are using as a key and they one you are using to search the key are different.
Every time you call SkinnyEmployee.new it will create a new, different, object. For example
employee_1 = SkinnyEmployee.new("123", "xyz")
employee_2 = SkinnyEmployee.new("123", "xyz")
employee_1 == employee_1 #=> true
employee_2 == employee_2 #=> true
employee_2 == employee_1 #=> false
If you call object_id on both employee_1 and employee_2 you will notice that it gives you different id's.
Using has_key? will check for the exact same object, and that won't be the case if you use SkinnyEmployee.new("456", "abc").
You would need a way to retrieve the exact same object, store it in a variable or in the DB, you are using as a key and use it as an attribute for has_key? for it to work.
Hope this can help you.

can you pass self to lambda in rails?

I want to define a class method that has access to a local variable. So this would be different for each instance of the class. I know you can make a class method dynamic with lambda like when you use it with named_scope. But can this be done for values that are specific to an instance?
In detail it is the has_attached_file method for the paperclip plugin in rails. I want to pass a lambda for the styles hash so that the image styles can be based off of attributes of the object stored in the DB. Is this possible?
Disclaimer: First, the question (Can you pass self to lambda?) and the problem you're trying to solve (dynamic styles with paperclip) don't fully match up. I won't answer the original question because it's not entirely related to your problem, and rampion took a valiant stab at it.
I'll instead answer your paperclip question.
In detail it is the has_attached_file method for the paperclip plugin in rails. I want to pass a lambda for the styles hash so that the image styles can be based off of attributes of the object stored in the DB. Is this possible?
Yes, it is possible. In paperclip, the :styles option can take a Proc. When the attachment is initialized, if a Proc was used, the attachment itself is passed to the Proc. The attachment has a reference to the associated ActiveRecord object, so you can use that to determine your dynamic styles.
For example, your has_attached_file declaration might look something like this (assuming a User and avatar scenario where the user can customize the size of their avatar):
class User < ActiveRecord::Base
has_attached_file :avatar, :styles => lambda { |attachment|
user = attachment.instance
dimensions = "#{user.avatar_width}x#{user.avatar_height}#"
{ :custom => dimensions }
}
end
Ok, you're being unclear.
Local variables in ruby begin with a lowercase letter (like foo, bar, or steve), and are lexically scoped (like C variables). They have nothing to do with "an instance of a class"
Instance variables in ruby begin with an # sigil (like #foo, #bar, or #carl), and are in scope whenever the current value of self is the object they are stored in.
If you want a method that can access the instance variables of an object directly, that's called an instance method. For example, battle_cry and initialize are both instance methods:
class Character
def initialize(name)
#name=name
end
def battle_cry
#name.upcase + "!!!"
end
def Character.default
new("Leeroy Jenkins")
end
end
A class method, by contrast, is a method for a Class object, and doesn't have access to any of the instance variables of that object. In the above example,
default is a class method.
If you want a (class or instance) method that triggers a change in or gets a value from the current scope, ruby uses a type of callback called a block.
class Character
ATTACKS = [ "Ho!", "Haha!", "Guard!", "Turn!", "Parry!", "Dodge!", "Spin!", "Ha", "THRUST!" ]
def attack
ATTACKS.inject(0) { |dmg, word| dmg + yield(word) }
end
end
person = Character.default
puts person.battle_cry
num_attacks = 0;
damage = person.attack do |saying|
puts saying
num_attacks += 1
rand(3)
end
puts "#{damage} points of damage done in #{num_attacks} attacks"
In the above example, attack uses the yield keyword to call the block passed
to it. When we call attack, then, the local variable num_attacks is still
in scope in the block we pass it (delimited here by do ... end), so we can
increment it. attack is able to pass values into the block, here
they are captured into the saying variable. The block also passes values
back to the method, which show up as the return value of yield.
The word lambda in ruby usually means the lambda keyword, which is used
to make blocks into freestanding, function like objects (which themselves are usually
referred to as lambdas, procs, or Procs).
bounce = lambda { |thing| puts "I'm bouncing a #{thing}" }
bounce["ball"]
bounce["frog"]
So I think what you're asking is whether you can pass a Proc in place of a Hash
for an argument to a method. And the answer is "it depends". If the method only
ever uses the #[] method, then yes:
class Character
attr_accessor :stats
def set_stats(stats)
#stats = stats
end
end
frank = Character.new("Victor Frankenstein")
frank.set_stats({ :str => 7, :dex => 14, :con => 9, :int => 19, :wis => 7, :cha => 11 })
monster = Character.new("Frankenstein's Monster")
monster.set_stats(lambda do |stat_name|
rand(20)
end)
However, it might use some other Hash specific methods, or call the same key multiple times,
which can produce weird results:
monster = Character.new("Frankenstein's Monster")
monster.set_stats(lambda do |stat_name|
rand(20)
end)
monster.stats[:dex] #=> 19
monster.stats[:dex] #=> 1
In which case, you may be better off caching the requests in an intermediate hash. This is fairly easy,
since a Hash can have an initializer block. So if we change the above to:
monster.set_stats(Hash.new do |stats_hash, stat_name|
stats_hash[stat_name] = rand(20)
end)
monster.stats[:dex] #=> 3
monster.stats[:dex] #=> 3
The results are cached in the hash
To see more about Hash block initializers, see ri Hash::new:
-------------------------------------------------------------- Hash::new
Hash.new => hash
Hash.new(obj) => aHash
Hash.new {|hash, key| block } => aHash
------------------------------------------------------------------------
Returns a new, empty hash. If this hash is subsequently accessed
by a key that doesn't correspond to a hash entry, the value
returned depends on the style of new used to create the hash. In
the first form, the access returns nil. If obj is specified, this
single object will be used for all default values. If a block is
specified, it will be called with the hash object and the key, and
should return the default value. It is the block's responsibility
to store the value in the hash if required.
h = Hash.new("Go Fish")
h["a"] = 100
h["b"] = 200
h["a"] #=> 100
h["c"] #=> "Go Fish"
# The following alters the single default object
h["c"].upcase! #=> "GO FISH"
h["d"] #=> "GO FISH"
h.keys #=> ["a", "b"]
# While this creates a new default object each time
h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" }
h["c"] #=> "Go Fish: c"
h["c"].upcase! #=> "GO FISH: C"
h["d"] #=> "Go Fish: d"
h.keys #=> ["c", "d"]

Resources