Ruby: Allowing a module to have settings - ruby

If I'm building a library in ruby, what's the best way to allow users of the library to set module-wide settings that will be available to all sub classes etc of the library?
A good example would be if I'm writing a library for posting to a webservice:
TheService::File.upload("myfile.txt") # Uploads anonymously
TheService::Settings.login("myuser","mypass") # Or any other similar way of doing this
TheService::File.upload("myfile.txt") # Uploads as 'myuser'
The idea would be that unless TheService::Settings.logout is called then all TheService operations would be conducted under myuser's account.
Any ideas?

store the data in class variables (or static varibales). You can do something like this:
module TheService
class Settings
def self.login(username,password)
##username = username
##password = password
end
def username
##username
end
def password
##password
end
def self.logout
##username = ##password = nil
end
end
end
Now you can access these setting from Everywhere via TheService::Settings.username or TheService::Settings.password

Related

Ruby Access Class Variables Easily

I've created a class that I'm using to store configuration data. Currently the class looks like this:
class Configed
##username = "test#gmail.com"
##password = "password"
##startpage = "http://www.example.com/login"
##nextpage = "http://www.example.com/product"
##loginfield = "username"
##passwordfield = "password"
##parser = "button"
##testpage = "http://www.example.com/product/1"
##button1 = "button1"
def self.username
##username
end
def self.password
##password
end
def self.startpage
##startpage
end
def self.nextpage
##nextpage
end
def self.loginfield
##loginfield
end
def self.passwordfield
##passwordfield
end
def self.parser
##parser
end
def self.testpage
##testpage
end
def self.button1
##button1
end
end
To access the variables I'm using:
# Config file
require_relative 'Configed'
# Parse config
startpage = Configed.startpage
loginfield = Configed.loginfield
passwordfield = Configed.passwordfield
username = Configed.username
password = Configed.password
nextpage = Configed.nextpage
parser = Configed.parser
testpage = Configed.testpage
This is not very modular. Adding additional configuration data needs to be referenced in three places.
Is there a better way of accomplishing this?
You can make class level instance variables...
class Configed
#username = "test#gmail.com"
#password = "password"
#startpage = "http://www.example.com/login"
# ...
class << self
attr_reader :username, :password, :startpage # ...
end
end
It's somewhat more compact, and still gives you
username = Configed.username
# ...
NOTE: there's a lot of good ideas in #philomory 's answer that deserves consideration. The use of YAML in particular would allow you to set up different constants for different environemnts test, development, production etc, and you can load the current environment's configuration options into an OpenStruct created in an initializer. Makes for a more flexible solution.
There are a lot of potential improvements. First of all, no reason to use class variables if you don't want their weird specific inheritance-related behavior, and no reason to use a class at all if you're not going to instantiate it.
You could use a module:
module Configed
module_function
def username
'username'
end
# etc
end
Configed.username
But frankly, you're almost certainly better off using a hash:
Config = {
username: 'username'
# etc
}.freeze
Config[:username]
or, if you prefer method-style access, an OpenStruct:
require 'openstruct' # from standard library
Config = OpenStruct.new(
username: 'username'
# etc
).freeze
Config.username
If they need to be modifiable, just don't freeze them. Also, typically a constant which is not a class or a module (such as a hash) would have a name in ALL_CAPS, e.g. CONFIGED, but, that's a stylistic decision with no actual impact on the code.
Your question refers to 'parsing' the config, but of course, you're not; the config data in your setup (and in my examples so far) is just Ruby code. If you'd rather load it from a non-code file, there's always YAML:
config.yaml:
username: username
password: password
config.rb:
require 'yaml' # from Standard Library
Configed = YAML.load_file('config.yaml')
Configed['username']
or JSON:
config.json:
{
"username": "username",
"password": "password"
}
config.rb:
require 'json' # from Standard Library
Configed = JSON.parse(File.read('config.json'))
Configed['username']

Using YAML data in Ruby App

I want to load data from my YAML file and use the it in my ruby application. The contents available in YAML will be used in different places. Therefore, I want to read it initially and use the data whenever required.
I would like to know what is the best practice for this?
What I tried is
config.yaml
db:
username: admin
password: admin
config.rb
class Config
class << self
attr_accessor :uname, :pwd
def load
config_data = YAML.load_file("c:\config.yml")
#uname = config_data['db']['username']
#pwd = config_data['db']['password']
end
end
end
my_app.rb
Config.load
puts Config.uname
puts Config.pwd
Please let me know whether this is a right way to load and use YAML data. If not please share the best practice.
It hardly depends on your intentions, but generally speaking, yes, it is mostly correct approach. What do you want to do more, is to cache once read data:
class Config
class << self
attr_accessor :uname, :pwd
def load force_reload = false
return #config_data if #config_data && !force_reload
#config_data = YAML.load_file("c:\config.yml")
#uname = #config_data['db']['username']
#pwd = #config_data['db']['password']
end
end
end
Here we return immediately, if the #config was already loaded, allowing fast subsequent calls to Config.config.
Whether one wants to explicitly reload the config, it should pass the parameter to load:
Config.load true # true means reload YAML explicitly

Who can explain how this code block works: Carrierwave MD5 as filename

I am using the Carrierwave gem with a Sinatra app to upload and store images.
I am following How to: Use file's MD5 as filename and everything works as expected. However I do not understand how the following piece of code provided on the how-to page works:
class ImageUploader < CarrierWave::Uploader::Base
storage :file
def md5
chunk = model.send(mounted_as)
#md5 ||= Digest::MD5.hexdigest(chunk.read)
end
def filename
#name ||= "#{md5}#{File.extname(super)}" if super
end
end
I in particular do not understand what model.send(mounted_as)does, what the ||= operator means, and why the if super conditional is used (and what it does).
Could somebody please explain this to me?
Say, for example the model is Person and the ImageUploader is mounted as avatar.
class Person < ActiveRecord::Base
mount_uploader :avatar, ImageUploader
end
Then, the md5 method would be calling something to the affect of chunk = person.avatar and using this to calculate the hash of the file contents, which you want for the name.
The filename method checking to see if there is a #filename instance variable, as defined in the CarrierWave::Uploader::Store class.
class CarrierWave::Uploader::Store
def filename
#filename
end
end
Then calling this again to get the filename extension to use in the constructed filename. The #name variable is then just a temporary cache of the name, so that future calls to the method do not require the whole thing to be calculated again.
Edit:
The carrierwave uploader has methods/instance variables for the model (eg Person instance) and declared mount point in the model (eg avatar). These are from the mount_uploader declaration in your active record model.
module CarrierWave::Uploader::Mountable
attr_reader :model, :mounted_as
def initialize(model=nil, mounted_as=nil)
#model = model
#mounted_as = mounted_as
end
end
These are used for various things, as well as being made available for us to do things just such as you are trying. It is just an abstract way to call person.avatar, which returns the file (File instance not string path). This is then read into the MD5 lib, which gives the hexdigest.
Rewriting this in more plain terms
class ImageUploader < CarrierWave::Uploader::Base
def md5
uploaded_file = model.send(mounted_as) # person.avatar (File instance)
#md5 ||= Digest::MD5.hexdigest(uploaded_file.read) # hexdigest of file content
end
end

Access module Configuration constant from another class

I am trying to understand how the following code is able to do this:
attr_accessor *Configuration::VALID_CONFIG_KEYS
Without requiring the Configuration file. Here is part of the code:
require 'openamplify/analysis/context'
require 'openamplify/connection'
require 'openamplify/request'
module OpenAmplify
# Provides access to the OpenAmplify API http://portaltnx20.openamplify.com/AmplifyWeb_v20/
#
# Basic usage of the library is to call supported methods via the Client class.
#
# text = "After getting the MX1000 laser mouse and the Z-5500 speakers i fell in love with logitech"
# OpenAmplify::Client.new.amplify(text)
class Client
include OpenAmplify::Connection
include OpenAmplify::Request
attr_accessor *Configuration::VALID_CONFIG_KEYS
def initialize(options={})
merged_options = OpenAmplify.options.merge(options)
Configuration::VALID_CONFIG_KEYS.each do |key|
send("#{key}=", merged_options[key])
end
end
....
end
And this is the Configuration module:
require 'openamplify/version'
# TODO: output_format, analysis, scoring can be specied in the client and becomes the default unless overriden
module OpenAmplify
# Defines constants and methods for configuring a client
module Configuration
VALID_CONNECTION_KEYS = [:endpoint, :user_agent, :method, :adapter].freeze
VALID_OPTIONS_KEYS = [:api_key, :analysis, :output_format, :scoring].freeze
VALID_CONFIG_KEYS = VALID_CONNECTION_KEYS + VALID_OPTIONS_KEYS
DEFAULT_ENDPOINT = 'http://portaltnx20.openamplify.com/AmplifyWeb_v21/AmplifyThis'
DEFAULT_HTTP_METHOD = :get
DEFAULT_HTTP_ADAPTER = :net_http
DEFAULT_USER_AGENT = "OpenAmplify Ruby Gem #{OpenAmplify::VERSION}".freeze
DEFAULT_API_KEY = nil
DEFAULT_ANALYSIS = :all
DEFAULT_OUTPUT_FORMAT = :xml
DEFAULT_SCORING = :standard
DEFAULT_SOURCE_URL = nil
DEFAULT_INPUT_TEXT = nil
attr_accessor *VALID_CONFIG_KEYS
....
end
This is from this repository: OpenAmplify
First of all, in both configuration.rb and client.rb, they're using the same naming space, which is module OpenAmplify.
Even though configuration.rb is not required in client.rb, the convention of Ruby project usually requires all necessary files in one file (normally the same name as the name space, and placed in {ProjectName}/lib/, in this case the file is openamplify/lib/openamplify.rb).
So if you go to openamplify/lib/openamplify.rb, you'll notice it actually requires all those two files:
require 'openamplify/configuration'
require 'openamplify/client'
And since constants are already defined in configuration.rb:
module OpenAmplify
module Configuration
VALID_CONFIG_KEYS = ...
end
end
Then obviously constant VALID_CONFIG_KEYS is visible in the same module (re-opened by client.rb) by Configuration::VALID_CONFIG_KEYS (and the * in front just means exploding array, because VALID_CONFIG_KEYS is an array of symbols)
module OpenAmplify
class Client
attr_accessor *Configuration::VALID_CONFIG_KEYS
end
end

Access Sinatra settings from a model

I have a modular Sinatra app. I'm setting some custom variables in my configure block and want to access these settings in my model.
The problem is, I get a NoMethodError when I try and access my custom settings from MyModel. Standard settings still seem to work fine though. How can I make this work?
# app.rb
require_relative 'models/document'
class App < Sinatra::Base
configure do
set :resource_path, '/xfiles/i_want_to_believe'
end
get '/' do
#model = MyModel.new
haml :index
end
end
# models/my_model.rb
class MyModel
def initialize
do_it
end
def do_it
...
settings.resource_path # no method error
...
settings.root # works fine
end
end
i think that you should be able to access it via
Sinatra::Application.settings.documents_path
I ended up doing:
#document.rb
class Document
def self.documents_path=(path)
#documents_path = path
end
def self.documents_path
#documents_path
end
...
end
#app.rb
configure do
set :documents_path, settings.root + "/../documents/"
Document.documents_path = settings.documents_path
end
then just using Document.documents_path inside my find method.

Resources