Currently, I'm working on the application which is developed in rails 3* and ruby 1.9.3. I have configured some email address in the initializers section as YML file for each environments.
But, the requirement is keep on changing (but it'll happened every month 1 or 2 times) that need to add/remove the email address from the configuration. Hence, I need to restart the server on every changes. Because, I configured those address in the initializers.
Is there any better way to handle this situation?
If you want to change the email without re-deploying / restarting the server, you can always create a Email model and persist it to the database. By adding a current field / column (boolean value) and a scope scope :current, -> { where(current: true) } you can access the email via Email.current.first.address, for instance. You might need to ensure that one and only one 'current' Email object ist present at any given time.
edit
creating a model does not mean you have to create UI for it. Just use the console to change the email if you have to.
the configuration and models are loaded at startup, and if you don't want to do any reloading in production, which is slow and not recommended, you have to use the DB for persistence.
if you really want to go down the "reload" route, set config.cache_classes = true in config/environments/production.rb and specify the email in a constant outside the config directory (in some model or controller) like this EMAIL = 'whatever#email.com'. You would have to change the production code on each production machine, without a server restart. Sounds very hacky. Look into zero-downtime deploys à la Github for a more elegant solution. Redeploying should be cheap and painless.
Related
I am working on an app where I now need to add test mode or sandbox mode features like Stripe provides in the dashboard. In this feature, when the user turned the test mode on, the user can test the functionality and can create dummy data in the same login session/token.
I have tried to use the Rails 6 feature to use multiple databases but have a few questions:
is it good to switch the connection in production for test mode even we will not have many test requests?
will it be good to have a separate instance for test mode with the test subdomain? in this case how we should manage the login sessions? should we copy data to the test the database? will it be good and common practice?
I only can manage to implement this if I have user data in the test database so that when I switch database connection system will not send an unauthenticated response.
Note:
for login and user update, it will use the primary database all time and for other actions, it will use the test_mode database. I am doing this by skip_around_action in a specific controller.
We are doing this so that when the user turned off test mode, it will update the primary database and the next request will use the primary database as per around_action logic
Here is my current code in application_controller.rb:
around_action :setup_db_connection
def setup_db_connection
database_key = (user.test_mode?) ? :test_mode : :primary
ActiveRecord::Base.connected_to(database: database_key) do
yield
end
end
test_mode database key has test database configuration in database.yml and similarly for primary database key. Both are completely two different databases.
Can anyone please tell me if I am going in to correct direction? Any help or thought will be appriciated. Thank you in advance!!!!
You should just create an additional environment as this is the exact problem that they are designed to solve.
Rails just ships preconfigured with development, test and production as thats the bare minimum you can get by with. You can actually have an unlimeted number of environments.
So lets say you want to create an enviroment named staging which closely matches your production environment. You can start by copying the productions settings:
cp app/environments/production.rb app/environments/staging.rb
And then just setup an additional hash in your database.yml and credentials files.
staging:
<<: *default
database: my_app_staging
You can then set the environment by using the RAILS_ENV env var or the command line arguments when starting the Rails server. Your staging environment can be run for example in a separate Heroku application made available on a subdomain. If you're using Heroku or some other SAAS platform a hobby tier will often be sufficient for staging.
When it comes to data you can either work on a set of dummy data generated by wiping and seeding or regularily wipe the staging database with a backup from your production database.
I would not consider using a "test mode switch" a good idea as it makes far to easy to inadvertantly mess things up. Using a separate environment lets you use a completely different set of credentials as well so you won't accentially do something like create a real credit card charge or destroy data on third party services. Sandboxes should be isolated.
I need to dynamically add a permitted host to my Rails 6 application during runtime.
I've managed to append to Rails.application.config.hosts at runtime, but I'm still receiving an error Blocked host.
Rails appears to ignore the hosts added to Rails.application.config.hosts that are added outside of application.rb and initializers.
Looking at host_authorization.rb, I can't see an obvious way of asking it to listen to new hosts.
My Reasoning:
User's can programmatically create their own 'shop' on our platform, which adds a new 'tenant' (apartment gem) to our multi-tenanted application We also allow them to point their own domains to our application.
However, in order to allow traffic from their custom domain, we currently would require a manual application restart if we stick with the default whitelisted domains.
I know I could use a workaround, and just set the hosts to whitelist everything (Rails.application.config.hosts = nil), but we need to keep the platform secure and automated.
The items in config.hosts are documented as compared using the === operator, which means we may include a lambda in that array, and thus make dynamic checking possible.
For the sake of an example, let's say we're returning a global permit-list from MyApp.all_permitted_domains. Then in config/application.rb we might write:
config.hosts << ->(host) {
MyApp.all_permitted_domains.include?(host)
}
Note however that in this case the additional processing normally performed on host authorization (such as handling port numbers, or the special case of a leading ".") is not included. If you need that behaviour too, review the sanitize_hosts method in that host_authorization.rb source file for how.
I'm building a system which allows the user to modify the database.yml contents via an admin frontend interface.
That changes to database.yml obviously don't have any affect until the application is restarted. Rather than forcing the user (who may not have SSH access to the physical box) to restar the application manually, I'd like a way to force ActiveRecord to reload the config post startup.
Rather than requiring them to restart the server, is there a way to force ActiveRecord to re-initialize after initial startup?
Rationale
There are two use cases for this - a) initial setup wizard b) moving from sqlite evaluation database to production supported database.
Initial Setup Wizard
After installing our application and starting it for the first time the user will be presented with a setup wizard, which amongst other things, allows the user to choose between the built in evaluation database using sqlite or to specify a production supported database. They need to specify the database properties. Rather than asking users to edit yml files on the server we wish the present a frontend to do so during initial setup.
Moving from sqlite evaluation database to production supported database
If the user opted to go with the built in evaluation database, but alter wants to migrate to a production database they need to update the database settings to reflect this change. Same reasons as above, a front end rather than requiring the user to edit config files is desired, especially since we can validate connectivity, permissions etc from the application rather than the user finding out it didn't work when they try to start the application and they get an activerecord exception.
Restart your Rails stack on the next request just as you would if you had access to the server.
system("touch #{File.join(Rails.root,'tmp','restart.txt')")
Building on #wless1's answer in order to get ERB-like ENV vars (e.g. <%= ENV['DB_HOST'] %>) working try this:
YAML::load(ERB.new(File.read(File.join(Rails.root, "config/database.yml"))).result)
ActiveRecord::Base.establish_connection config[Rails.env]
Theoretically, you could achieve this with the following:
config = YAML::load File.read(File.join(Rails.root, "config/database.yml"))
ActiveRecord::Base.establish_connection config[Rails.env]
You'd have to execute the reconnection code in every rails process you're running for it to be effective. There's a bunch of dangerous things to worry about here though - race conditions when changing the database config, syntax problems in the new database.yml, etc.
How can I get the domain and subdomains of my application without being in a controller/view?
The reason for needing this is that I am subscribing to process_action.action_controller notifications within an initializer, and I need my applications' url from within that initializer.
If the host part of the URL (domain, subdomain) is dynamic ... which is to say "depends on the request" then, of course, the only way to get it is within the context of the request itself, which the controllers and views know about.
So I am assuming the application has a known host, perhaps dependent upon runtime environment (production, test, development, etc.), or maybe based on the server environment, but otherwise static. In this case, you could define a custom config variable containing the name, as noted in the more recent answer from Jack Pratt on this SO question: How to define custom configuration variables in rails.
I'm really struggling to understand the routing behaviour I'm seeing with a Rails 3 app as it relates to Passenger and routing helper methods. Here's a quick overview:
When a user registers for my site, one of the things I'd like to do is send out a verification email to make sure the user has submitted a valid email address. So, I set up mailer to send the user a URL with some sort of magic token, thereby proving that they have received the email I sent. The URL in the body of the email is generated via a routing helper method (in this case, verify_url("a1b2c3") ) and the URL is stuffed into the email appropriately as:
http://localhost:3000/verify/a1b2c3
This all works great under localhost:3000 without a hitch. So, like a responsible developer, the next thing I do is move my app to my remote QA environment and verify that everything still works. My hosting provider uses Passenger, and I have it set up so that my QA environment can be accessed as follows:
http://my.url/qa
This is done by setting RailsBaseURI to /qa in the appropriate .htaccess file. So, I'm trying to register for the site in my QA environment, and I'm hoping that the helper method verify_url generates the following URL:
http://my.url/qa/verify/a1b2c3
However, this is what I get instead:
http://my.url/verify/a1b2c3
Notice the distinct lack of reference to the 'qa' path? I've scoured various blogs and manuals looking for an answer, and I've seen the following suggestions:
Set an environment variable such as ENV['RAILS_RELATIVE_URL_ROOT']
Set ActionController::Base.relative_url_root in my environment/qa.rb
Add a map to my config.ru to initialize my app under a particular path
Wrap my routes in a scope
However, each of these seems either to be deprecated (the first two options), or ends up producing redundant path info (the second two options) as follows:
http://my.url/qa/qa/verify/a1b2c3
Can anyone tell me the proper way to set this up, such that when I call verify_url("a1b2c3"), I'm actually getting the proper URL, including the relative path for my QA environment? Ideally, I'd want to be able to set this in environments/qa.rb somehow, such that I don't have to change my app at all when moving from one environment to the next.
Thanks in advance!
I would dismiss the whole /qa/ and move the staging to a subdomain like http://qa.my.url/ Otherwise you will have to make changes to the code that might affect production in a negative way.
Update
I've ignored the the fact that you wanted to use those routes in emails. In this case you will have to set :host in the url helper:
verify_url(:id=>"a1b2c3", :host=>"qa.my.url")
To cut down the amount of changes you would have to make once you go into production, I would suggest you define a constant in an initializer (in /path/to/app/config/initializers/my_host.rb):
MY_HOST = 'qa.my.url'
After that you could call verify_url in your email templates like so:
verify_url(:id=>"a1b2c3", :host=>MY_HOST)
Once you go into production, change the MY_HOST constant and you won't have to worry about the templates.
In my views I never use the *_url helpers, only the *_path ones to circumvent this issue.
After spending a little time with the ActionMailer docs, it turns out that there is an even easier solution, at least for what I am looking to accomplish. Wukerplank definitely answered my question, but it seems that I wasn't quite asking the right question. :-)
The only time I really need to see the fully qualified URL is when I'm working within a mailer, right? Otherwise, paths work just fine. Well, it turns out that ActionMailer allows you to specify default url options on a per-environment basis. In my environments/development.rb config file, I now have the following:
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
When I move to my QA environment, I just need to tell Passenger that I'm running from QA, and have something like this environments/qa.rb config:
config.action_mailer.default_url_options = { :host => 'qa.my.url' }
That way, the code doesn't have to change one bit between environments, and thus, I really don't care what domain, subdomain, or port I'm running on... It just works. Hope this helps someone else in the future. Thanks!