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].
Related
I'm looking to make my db:seed task a little more dynamic by allowing certain entries in my application to be created or updated (or nothing at all) based on the values of in db/seeds.rb and the current state of the backend database. I'm also looking to simplify the functions that carry this logic out.
I'd like to avoid this:
def create_or_update_config(key, value)
...
if config_entry.value != value
config_entry.value
end
if config_entry.changed?
config_entry.save
end
...
end
And instead have something simplistic:
def create_or_update_config(key, value)
...
config_entry.value = value
if config_entry.changed?
config_entry.save
end
...
end
Is ActiveRecord smart enough to know if attribute values have changed even if they've been set (with the same value)?
Yes. ActiveRecord's changed? method will return true only if there were mutations since the last save. From an example using my own app's console (Rails 5.1.6):
irb(main):013:0> config.value
=> "http://localhost:3100"
irb(main):014:0> config.value = "http://localhost:3100"
=> "http://localhost:3100"
irb(main):015:0> config.changed?
=> false
irb(main):016:0> config.value = "anything else"
=> "anything else"
irb(main):017:0> config.changed?
=> true
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)
We are encrypting values in table / model - RegisteredDomain, using attr_encrypted below
attr_encrypted :domain_name, :registered_by, :api_key, :key => "65xU4TdZntTV53"
Values get encrypted and stored to db as well. Below code saves values to db.
registereddomain = RegisteredDomain.new(
:domain_name => domain_name,
:api_key => api_key,
:hash_key => hash_key,
:registered_by => ep_id,
:status => status,
:domain_type_id => domain_type_id
)
registereddomain.save
Problem is with decrypting values with below select. We get encrypted values here, could anyone tell how to get decrytpted values in listing.
def select_all
#registered_domains = RegisteredDomain.select("id, encrypted_domain_name, domain_type_id, encrypted_api_key, status").order(updated_at: :desc)
return #registered_domains
end
There is probably a reason to store values in the database in encrypted mode. Hence if you want to decrypt the value, just call the appropriate method on an instance method:
#registered_domains = RegisteredDomain.select("id, encrypted_domain_name, domain_type_id, encrypted_api_key, status").order(updated_at: :desc)
#registered_domains.first.domain_name
Edit
you migth want to create a custom method for pulling the values from the database:
def self.all_decrypted(columns)
all.map do |record|
columns.map do |column|
record.send(column.to_sym)
end
end
end
RegisteredDomain.all_decrypted([:domain_name, :api_key]) This will return you an array of arrays, actual values of domain_name, api_key.
It'll be good if you can give some sample code of yours for controller and view as well..
As I am seeing you are directly using fields without the attribute option; so in your database the field domain_name mus be represented as encrypted_domain_name. So here in your select query you can try to modify your the attribute encrypted_domain_name to domain_name.
Also you should modify it where-ever you want to get the encrypted attribute.
(I am not able to write the complete code-block properly hence I tried to explain it in short; but just renaming the attribute should work!)
In Grails, if I define a locale, and put a date on specific format on i18n file, like (dd/mm/AAAA), if call one request like:
http://myapp/myaction?object.date=10/12/2013
When I get print: params.date, it comes to me a date object.
How can I do the same on rails?
Normally the Rails handles this for you. For instance, the form helper datetime_select works in conjunction with some activerecord magic
to ensure ensure time/date types survive the round-trip. There are various alternatives to the standard date-pickers.
If this doesn't work for you e.g. rails isn't generating the forms, there are (at least) a couple of options.
One option, slightly evi, is to monkey-patch HashWithIndifferentAccess (used by request params) to do type conversions based on the key name. It could look something like:
module AddTypedKeys
def [](key)
key?(key) ? super : find_candidate(key.to_s)
end
private
# look for key with a type extension
def find_candidate(key)
keys.each do |k|
name, type = k.split('.', 2)
return typify_param(self[k], type) if name == key
end
nil
end
def typify_param(value, type)
case type
when 'date'
value.to_date rescue nil
else
value
end
end
end
HashWithIndifferentAccess.send(:include, AddTypedKeys)
This will extend params[] in the way you describe. To use it within rais, you can drop it into an initialiser, eg confg/initializers/typed_params.rb
To see it working, you can test with
params = HashWithIndifferentAccess.new({'a' => 'hello', 'b.date' => '10/1/2013', 'c.date' => 'bob'})
puts params['b.date'] # returns string
puts params['b'] # returns timestamp
puts params['a'] # returns string
puts params['c'] # nil (invalid date parsed)
However... I'm not sure it's worth the effort, and it will likely not work with Rails 4 / StrongParameters.
A better solution would be using virtual attributes in your models. See this SO post for a really good example using chronic.
Is there a way to get the actual columns name with ActiveRecord?
When I call find_by_sql or select_all with a join, if there are columns with the same name, the first one get overridden:
select locations.*, s3_images.* from locations left join s3_images on s3_images.imageable_id = locations.id and s3_images.imageable_type = 'Location' limit 1
In the example above, I get the following:
#<Location id: 22, name: ...
>
Where id is that of the last s3_image. select_rows is the only thing that worked as expected:
Model.connection.select_rows("SELECT id,name FROM users") => [["1","amy"],["2","bob"],["3","cam"]]
I need to get the field names for the rows above.
This post gets close to what I want but looks outdated (fetch_fields doesn't seem to exist anymore How do you get the rows and the columns in the result of a query with ActiveRecord? )
The ActiveRecord join method creates multiple objects. I'm trying to achieve the same result "includes" would return but with a left join.
I am attempting to return a whole lot of results (and sometimes whole tables) this is why includes does not suit my needs.
Active Record provides a #column_names method that returns an array of column names.
Usage example: User.column_names
two options
Model.column_names
or
Model.columns.map(&:name)
Example
Model named Rabbit with columns name, age, on_facebook
Rabbit.column_names
Rabbit.columns.map(&:name)
returns
["id", "name", "age", "on_facebook", "created_at", "updated_at"]
This is just way active record's inspect method works: it only lists the column's from the model's table. The attributes are still there though
record.blah
will return the blah attribute, even if it is from another table. You can also use
record.attributes
to get a hash with all the attributes.
However, if you have multiple columns with the same name (e.g. both tables have an id column) then active record just mashes things together, ignoring the table name.You'll have to alias the column names to make them unique.
Okay I have been wanting to do something that's more efficient for a while.
Please note that for very few results, include works just fine. The code below works better when you have a lot of columns you'd like to join.
In order to make it easier to understand the code, I worked out an easy version first and expanded on it.
First method:
# takes a main array of ActiveRecord::Base objects
# converts it into a hash with the key being that object's id method call
# loop through the second array (arr)
# and call lamb (a lambda { |hash, itm| ) for each item in it. Gets called on the main
# hash and each itm in the second array
# i.e: You have Users who have multiple Pets
# You can call merge(User.all, Pet.all, lambda { |hash, pet| hash[pet.owner_id].pets << pet }
def merge(mainarray, arr, lamb)
hash = {}
mainarray.each do |i|
hash[i.id] = i.dup
end
arr.each do |i|
lamb.call(i, hash)
end
return hash.values
end
I then noticed that we can have "through" tables (nxm relationships)
merge_through! addresses this issue:
# this works for tables that have the equivalent of
# :through =>
# an example would be a location with keywords
# through locations_keywords
#
# the middletable should should return as id an array of the left and right ids
# the left table is the main table
# the lambda fn should store in the lefthash the value from the righthash
#
# if an array is passed instead of a lefthash or a righthash, they'll be conveniently converted
def merge_through!(lefthash, righthash, middletable, lamb)
if (lefthash.class == Array)
lhash = {}
lefthash.each do |i|
lhash[i.id] = i.dup
end
lefthash = lhash
end
if (righthash.class == Array)
rhash = {}
righthash.each do |i|
rhash[i.id] = i.dup
end
righthash = rhash
end
middletable.each do |i|
lamb.call(lefthash, righthash, i.id[0], i.id[1])
end
return lefthash
end
This is how I call it:
lambmerge = lambda do |lhash, rhash, lid, rid|
lhash[lid].keywords << rhash[rid]
end
Location.merge_through!(Location.all, Keyword.all, LocationsKeyword.all, lambmerge)
Now for the complete method (which makes use of merge_through)
# merges multiple arrays (or hashes) with the main array (or hash)
# each arr in the arrs is a hash, each must have
# a :value and a :proc
# the procs will be called on values and main hash
#
# :middletable will merge through the middle table if provided
# :value will contain the right table when :middletable is provided
#
def merge_multi!(mainarray, arrs)
hash = {}
if (mainarray.class == Hash)
hash = mainarray
elsif (mainarray.class == Array)
mainarray.each do |i|
hash[i.id] = i.dup
end
end
arrs.each do |h|
arr = h[:value]
proc = h[:proc]
if (h[:middletable])
middletable = h[:middletable]
merge_through!(hash, arr, middletable, proc)
else
arr.each do |i|
proc.call(i, hash)
end
end
end
return hash.values
end
Here's how I use my code:
def merge_multi_test()
merge_multi!(Location.all,
[
# each one location has many s3_images (one to many)
{ :value => S3Image.all,
:proc => lambda do |img, hash|
if (img.imageable_type == 'Location')
hash[img.imageable_id].s3_images << img
end
end
},
# each location has many LocationsKeywords. Keywords is the right table and LocationsKeyword is the middletable.
# (many to many)
{ :value => Keyword.all,
:middletable => LocationsKeyword.all,
:proc => lambda do |lhash, rhash, lid, rid|
lhash[lid].keywords << rhash[rid]
end
}
])
end
You can modify the code if you wish to lazy load attributes that are one to many (such as a City is to a Location) Basically, the code above won't work because you'll have to loop through the main hash and set the city from the second hash (There is no "city_id, location_id" table). You could reverse the City and Location to get all the locations in the city hash then extract back. I don't need that code yet so I skipped it =)