Ruby and Rails: Metaprogramming variables to become class methods - ruby-on-rails

I'm creating a model called Configuration and I have the following code and I want to make it more dynamic by using metaprogramming.
In a table on the database for Configuration model I have the following data.
---------------------------------------------------------
variable_name as string | value in text
|
company_name | MyCompany
welcome_text | Welcome to MyCompany's App!
email_order_text | You've just created an account with MyCompany.
year_since | 2012
----------------------------------------------------------
class Configuration < ActiveRecord::Base
#nothing here yet
end
----------------------------------------------------------
Currently, the only way to access the company_name is to do the following in rails console:
configuration_company_name = Configuration.find_by_variable_name("company_name")
configuration_company_name.company_name
> "MyCompany"
I think this is an unacceptable way to do things. First, it will access the database everytime someone checks for the company's name. I think if I could load it when the app starts and doesn't have to access it again because it's in the memory, then it would be better. How can I do something more dynamic so I could access the value "MyCompany" like this.
Configuration.company_name
> "MyCompany"
The reason to do this is to give allow fast customization of the application.

class Configuration < ActiveRecord::Base
# loads all the configuration variables to an in-memory
# static hash during the first access.
def self.[](n)
#config ||= {}.tap { |h| Configuration.all.each{ h[variable_name] = c.value}}
#config[n]
end
end
Now you can access your configuration as :
Configuration["company_name"]
If you a large number of configuration parameters, it might be beneficial to pre-load the cache by accessing a configuration parameter in an initializer file. If you have 1000s of configuration variables you might have to consider migrating the cache to memcached etc.
If you want to access the configuration parameter as a class method:
class Configuration < ActiveRecord::Base
klass = class << self; self; end
Configuration.all.each{|c| klass.send(:define_method, c.variable_name){c.value}}
end
Now you can access the parameter as follows:
Configuration.company_name

One thing you are getting wrong here,it will be never be Configuration.company_name , thats like access a Class property instead of a Object/Instance property,
It should be a instance of the Configuration Class. It would still be somewhat acceptable to use #KandagaBoggu's method in the other answer, but database access still almost everytime or from the Active Record’s query cache.But AR's query cache lives for the duration of a particular action (i.e. request ). You may want use something like Memcached for the objects to survive longer.

We can move these constant values into a yml file, when the server starts load them into a variable and access it whenever needed.

Related

Rails Limit Model To 1 Record

I am trying to create a section in my app where a user can update certain site wide attributes. An example is a sales tax percent. Even though this amount is relatively constant, it does change every few years.
Currently I have created a Globals model with attributes I want to keep track of. For example, to access these attributes where needed, I could simply do something like the following snippet.
(1+ Globals.first.sales_tax) * #item.total
What is the best way to handle variables that do not change often, and are applied site wide? If I use this method is there a way to limit the model to one record? A final but more sobering question.......Am I even on the right track?
Ok, so I've dealt with this before, as a design pattern, it is not the ideal way to do things IMO, but it can sometimes be the only way, especially if you don't have direct disk write access, as you would if deployed on Heroku. Here is the solution.
class Global < ActiveRecord::Base
validate :only_one
private
def only_one
if Global.count >= 1
errors.add :base, 'There can only be one global setting/your message here'
end
end
end
If you DO have direct disk access, you can create a YAML config file that you can read/write/dump to when a user edits a config variable.
For example, you could have a yaml file in config/locales/globals.yml
When you wanted to edit it, you could write
filepath = "#{Rails.root}/config/locales/globals.yml"
globals = YAML.load(File.read("#{Rails.root}/config/locales/globals.yml"))
globals.merge!({ sales_tax: 0.07 })
File.write(filepath) do |f|
f.write YAML.dump(globals)
end
More on the ruby yaml documentation
You could also use JSON, XML, or whatever markup language you want
It seems to me like you are pretty close, but depending on the data structure you end up with, I would change it to
(1+ Globals.last.sales_tax) * #item.total
and then build some type of interface that either:
Allows a user to create a new Globals object (perhaps duplicating the existing one) - the use case here being that there is some archive of when these things changed, although you could argue that this should really be a warehousing function (I'm not sure of the scope of your project).
Allows a user to update the existing Globals object using something like paper_trail to track the changes (in which case you might want validations like those presented by #Brian Wheeler).
Alternatively, you could pivot the Global object and instead use something like a kind or type column to delineate different values so that you would have:
(1+ Globals.where(kind: 'Colorado Sales Tax').last) * #item.total
and still build interfaces similar to the ones described above.
You can create a create a class and dump all your constants in it.
For instance:
class Global
#sales_tax = 0.9
def sales_tax
#sales_tax
end
end
and access it like:
Global.sales_tax
Or, you can define global variables something on the lines of this post

Rails and class variables: will this persist across users and server calls?

We're on Rails 3.0.6.
Will the following code persist across page loads and users in Rails? Or does it get redefined with every request? The code lives at the top of a controller.
##list = []
hero = {}
hero['name'] = 'so'
hero['superpowers'] = ['coding', 'qa', 'spec_writing']
##list.push hero
hero2 = {}
hero2['name'] = 'so2'
hero2['superpowers'] = ['coding']
##list.push hero2
... more Hashes pushed into ##list
The list only changes once a month, and we would like to efficiently and quickly make this data available to page requests. We will eventually use Rails.cache to read from the database, but we're using the following approach for now (assuming it works).
In development, the default behavior is for the class to be reloaded on each request, thereby resetting your class variable. In production, however, the class is initialized once and so the class variable will persist across multiple requests and multiple sessions.
You should move to a proper caching technique as soon as you can. You cannot, obviously, persist the value beyond the reloading of the class when the application is restarted. Furthermore, if the web server is multi-threaded (as it is likely to be), it may be running multiple instances of the application on different threads which do not share the class variables, potentially creating inconsistencies between requests.
I've just came across the same issue and found this post. I know it's an old question, but posting my answer just in case someone else faces similar issue...
I think for cases where database or Rails.cache can not be used for some reason then the best place to put those values is Application class. Just define some attribute and initialize it. Then it's easy to access it just like Rails.application.heros. Quick and dirty sample below:
in config/application.rb
module YourRailsApp
class Application < Rails::Application
attr_reader :heros
initializer "init heros" do
#heros = []
hero = {}
hero['name'] = 'so'
hero['superpowers'] = ['coding', 'qa', 'spec_writing']
#heros.push hero
hero2 = {}
hero2['name'] = 'so2'
hero2['superpowers'] = ['coding']
#heros.push hero2
end
#Other application sutff...
end
end
I sometimes used what I called the cache of the poor.
It worked as follow:
in an initializer, add: MY_CACHE_HASH = {}
wherever you want: MY_CACHE_HASH[:foo] = :bar
wherever you need MY_CACHE_HASH[:foo] #=> :bar
I wouldn't recommend it though: what would you do if your server crashes?
If you have data that won't be changed while rails is running then it's fine to use a ##class_variable.
keep in mind that (in the default development configuration) controller classes are re-loaded at every request, so if you need to say, read the data from a file, consider putting the code to initialize the data into config/application.rb in a $global or CONSTANT_VAR.
So what you're describing is a datastructure that is not a database. I think it's perfectly reasonable to have items that "never" change in your code and not in your database.
for example, You could have:
In app/models/hero.rb
class Hero
##all_heros = []
def self.all_heros
##all_heros
end
def self.add_hero(hero)
##all_heros << hero
end
def initialize(name, superpowers=[])
#name = name
#superpowers = superpowers
end
end
# this will get executed on load
Hero.add_hero( Hero.new("so", ['coding', 'qa', 'spec_writing']))
Then later in your code, you'll access:
#heros = Hero.all_heros
And later you can swap out for a database backed store if you need on.

Permanent variable in Rails

Lets say that on top of my Rails app there is a bar with piece of text displayed - latest hot deal, scheduled downtime notfication, something like that. It's a single, on of a kind information that needs to be accessed on basically every request, and may be updated from time to time. What is the best way to achieve this?
What I'd like to do is some kind of permanent global variable (accessible from controllers).
It will be updated very rarely, so there's no problem if for some time after update there will be an inconsistency between workers.
On the other hand, it should be persistent in case of server fault (periodic backup is enough).
It will be accessed really often, so it should be as fast as possible - preferably stay in memory.
Also, it's only one of a kind, so I'd really prefer not to bloat the app with a dedicated database model.
Something like that is damn easy in Node.js for example, but I couldn't find a single way to achieve this in Rails. What shall I do?
EDIT
Thanks for the answers so far, but while they're inspiring, I think that I should stress out one key functionality that they're all missing. The variable should be editable inside the app and persistent. While it's possible to edit your variables, in case of server restart I'm back to the default - which is bad.
It really depends on what you are looking for. You could do something very simply by putting it in your application_controller.rb
class ApplicationController < ActionController::Base
def system_message
"Come buy our amazing .99 iphone chocolate bar apps, with 100% more gamification!"
end
end
That function (and string) is then accessible from any controller in your application. You could also specify something in the after_initialize block in your application.rb file.
config.after_initialize do
::MYTEXT = "MY SUPER AMAZING TEXT"
end
You could also create your own file under the initializers directory, which is preloaded in rails.
so siteAnnounce.rb
MYANNOUNCEMENT = "NOW LISTEN TO ME!"
You may also want to check out this Railscast video about site wide announcements
I would store it in the database and let caching take care of it.
I feel that global variables are fine, when appropriate, for code that needs to share that common value in many places but that is the code, not the the user view.
This is clearly true in this case as the OP has bolded 'editable by the app'. So I would have a view that lets the users enter it, it gets stored in a db table and then recalled as needed (as cached once used once).
Well I had faced a similar problem.
My problem was I needed a global variable in all the levels (MVC).
We went to use Memcache to store the variable.
May be you can go for a similar solution.
And as an added bonus you can change it throughout the program.
You could declare it as a constant in an initializer:
config/initialzers/foo.rb:
MYVARIABLE = 'some string'
Accessible from anywhere in your application as MYVARIABLE
Ok, so here's what I did. Instead of just putting the value to an initializer, I've made there a simple class that handles it. The variable itself is stored in a predefined file. Besides of reading the file upon the initialization, the class updates file when the value is changed, and also re-read the file periodically to maintain consistency across workers. I've also put there some basic JSON handling and backup functionality to make life easier.
For anyone interested, here's the important code:
class Pomegranate
def initialize
#delay = 30.minutes
#path = "db/pomegranate.json"
#valid = Time.now - 1
validate
end
def get(*p)
validate
p.inject(#data) {|object,key| object[key] if object}
end
def set(*p, q, v)
hash = p.inject(#data) {|object,key| object[key]||={}}
hash[q] = v
end
def save
#valid = Time.now + #delay
File.open(#path,"w") {|f| f.write(#data.to_json)}
end
private
def validate
if #valid < Time.now
#data = ActiveSupport::JSON.decode(File.read(#path)) rescue {}
#valid = Time.now + #delay
#valid = Time.now - 1 if #data.empty?
end
end
end
$pom = Pomegranate.new
Source:
Where to put Global variables in Rails 3
Try putting it in your applicaton.rb like this:
module MyAppName
class Application < Rails::Application
YOUR_GLOBAL_VAR = "test"
end
end
Then you can call it with the namespace in your controllers, views or whatever..
MyAppName::Application::YOUR_GLOBAL_VAR
Another alternative would be using something like settingslogic. With settingslogic, you just create a yml config file and a model (Settings.rb) that points to the config file. Then you can access these settings anywhere in your rails app with:
Settings.my_setting
I've started putting constants and variables like this in the configuration object, e.g.
TestApp::Application.config.foo = 'bar'
TestApp::Application.config.something = { :a => 1, :b => 2 }

How to persist Ruby class variables across page loads in Rails?

I have a class variable that I would like to set from an initilizer and have the value kept from then on. The example below works for only the first page load. Is there a better way to do this?
app/models/token.rb
class Token
class << self
attr_accessor :salt
end
end
config/initilizers/token.rb
Token.salt = "savory hash"
In development mode, your class is going to get reloaded with every request, so the value that's set in an initializer at app startup will not persist when the class is reloaded after the first request. (The result of "config.cache_classes = false" in your development.rb).
However, if you want to set a value in an initializer and have it persist in development mode, you can either add it as a constant:
initializers.rb
SALT='savory_hash'
OR as an application config variable:
application.rb
module YourAppsName
class Application < Rails::Application
config.token_salt = "savory_hash"
end
end
which would be accessible anywhere in the app with:
Rails.application.config.token_salt
Of course, if you enable class caching in your environment, you should find that your variable's value will persist without doing anything of the above.
You can try storing them in session variables, cache, or even within its own table (a reference table).

Thread safety of a class variable in Rails - will this work?

I'm using the Ruby Money gem in a multi-tenant (SaaS) Rails app, and am looking for a good way to make the Money.default_currency be set to an Account's preference for each request. I have several currency-related models in the app that use the Money class.
I have everything working properly in development, but I'm just looking for some feedback on whether or not the solution with have repercussions in production.
Here's what I did in my ApplicationController (irrelevant code removed for brevity):
class ApplicationController < ActionController::Base
before_filter :set_currency
private
def set_currency
Money.default_currency = Money::Currency.new(current_account.present? && current_account.currency.present? ?
current_account.currency : 'USD')
end
end
So the code above will set the default_currency class variable to the current account's preference, or default back to 'USD' if there isn't one.
By the way, here's the relevant default_currency code in the Money class:
class Money
# Class Methods
class << self
# The default currency, which is used when +Money.new+ is called without an
# explicit currency argument. The default value is Currency.new("USD"). The
# value must be a valid +Money::Currency+ instance.
#
# #return [Money::Currency]
attr_accessor :default_currency
end
end
So, will this work as expected in a multi-user setting? Anything else I need to do?
Most rails apps don't run in multithreaded mode - a given instance is only ever handling one request at a time (this is the default).
If your app was in multithreaded mode this would be dangerous - Money.default_currency could get changed halfway through a request by the new request that has just come in. If you did want to make this thread safe, you could use the Thread.current hash to have per thread values of default_currency

Resources