I am working right now on a Rails 4.0 application (using Ruby 2.0.0).
I would like to interact with Jenkins using jenkins_api_client gem, from multiple pages of my Rails application.
This gem generally using a #client parameter, which is initialized to contain the credentials and other information of the Jenkins server.
This parameter in initialized using something like this:
#client = JenkinsApi::Client.new(:server_ip => '0.0.0.0',
:username => 'somename', :password => 'secret password')
Once initialized, I would like to access this parameter and run multiple sub-routines on it.
This initialization takes time, and I really want to avoid doing this process every time one of the clients would like to use this gem functionality, such as:
# Get a filtered list of jobs from the server
jobs_to_filter = "^test_job.*"
jobs = #client.job.list(jobs_to_filter)
So, I hope to do this only once- when the rails server starts.
I would like to use this parameter from multiple pages of my app, possibly with threaded solution further down the road (not critical at the moment).
Can anyone recommend how to achieve this?
I'd appreciate an answer which is consistent with Rails convention.
Thanks a lot!
as example you could create something like that:
module JenkinsApi
class Client
class << self
attr_reader :instance, :config
def configure(&block)
#config = OpenStruct.new
block.call #config
end
def instance
#instance ||= JenkinsApi::Client.new #config
end
end
end
end
which allow you write in initializer:
JenkinsApi::Client.configure do |config|
config.server_ip = '0.0.0.0'
config.username = 'somename'
config.password = 'secret password'
end
and then use it like: JenkinsApi::Client.instance.job.list(...
Related
I have a Rails 3.2 application, and I want to use one database for many clients and one application. So for every model I have created a field called account_id, now I want to add a global scope for filtering the row in the base of the account_id of the logging user(account_id is a session param). So in initialize I have created a file and put these code
module ActiveRecord
# = Active Record Named \Scopes \
module Scoping
module Default
module ClassMethods
def unscoped #:nodoc:
return (block_given? ? relation.scoping { yield } : relation).where(account_id: Thread.current['account_id'].id)
end
def default_scope(scope = {})
scope = Proc.new if block_given?
if scope.kind_of?(Array) || scope.is_a?(Hash)
scope.merge!({:conditions=>{account_id:Thread.current['account_id'].id}})
end
self.default_scopes = default_scopes + [scope]
end
end
end
end
end
If I logged with user account_id=2 all is ok, but if in the same moment I logged on another browser or computer with account_id=3 ...I have many errors and on the log, I have seen that the application use account_id=2 but also account_id=3 at the same time.
Any solution? How I can rewrite default_scope(scope = {})? Other other idea?
Thread.current[] data is not unique per request. I used to have the same problem. By that time I had been using this gem https://github.com/steveklabnik/request_store. Hope it will help or at least give an idea.
I'm trying to implement zoom.us Rest API into my rails app (example: https://github.com/mllocs/zoomus and https://support.zoom.us/hc/en-us/sections/200305463-API ), but i have no idea where to put this part of the code. Or how to call it. (i know that i'm suppose to change the xxx to my credentials at least.) Is there a specific ruby gem that I should be installing first prior to working with a Rest API?
require 'zoomus'
Zoomus.configure do |c|
c.api_key = 'xxx'
c.api_secret = 'xxx'
end
zoomus_client = Zoomus.new
user_list = zoomus_client.user_list
user_list['users'].each do |user|
user_id = user['id']
puts zoomus_client.meeting_list(:host_id => user_id)
end
begin
user_list = zoomus_client.user_list!
rescue Zoomus::Error => exception
puts 'Something went wrong'
end
I suggest you that all configuration of zoom put into your config->environments->development.rb or productin.rb files like:
Zoomus.configure do |c|
c.api_key = 'xxx'
c.api_secret = 'xxx'
end
and put your other code into files where you want to access or use (api, controller,method,class)
zoomus_client = Zoomus.new
user_list = zoomus_client.user_list
user_list['users'].each do |user|
user_id = user['id']
puts zoomus_client.meeting_list(:host_id => user_id)
end
begin
user_list = zoomus_client.user_list!
rescue Zoomus::Error => exception
puts 'Something went wrong'
end
Hope this will help you.
If you're using Bundler, you don't need to first line. When you add gem 'zoomus' to your Gemfile, bundle, then run Rails, those gems will be required automatically unless explicitly told otherwise (using require: false option)
Normally the configure block goes into config/initializers/zoomus.rb. Any file inside of config/initializers/ will be loaded during the boot process.
The rest of the code really depends on when and where you use this service. My recommendation would be to wrap these calls into plain objects that sit in app/services. Call out to those classes wherever you need to, whether it's an ActiveJob worker, controller, or model.
Really it's impossible to answer your question without context, and even with context you will get different answers from different people. Where to place code has been a hot topic of debate in Rails for many years.
This stuff:
Zoomus.configure do |c|
c.api_key = 'xxx'
c.api_secret = 'xxx'
end
ZoomusClient = Zoomus.new
should be in a file config/initializers/zoomus.rb.
Note that I changed zoomus_client to ZoomusClient so that it is a constant. This is so it will be available in the other files.
Make sure you're not checking in the api credentials into source control, or they might get published to Github. Use dotenv or something for this.
The rest of the code can be called by any method in your rails app.
puts ZoomusClient.meeting_list(
host_id: ZoomusClient.user_list['users'][0].id
)
I have a rails 4.2 multi-tenant app using the Apartment gem which has been awesome.
Each company has their own subdomain. I'm using a custom "elevator" which looks at the full request host to determine which "Tenant" should be loaded.
When I create a new company I have an after_create hook to create the new tenant with the proper request host.
This always seems to require a restart of the server both in development and production otherwise I get a Tenant Not Found error.
It's using sqlite in development and postgres in production.
Do I really have to restart the server each time I create a new tenant? Is there an automated way to do this? Maybe just reloading the initializer will work, but I'm not sure how to do that/if that's possible?
I have been messing around with this for a month and haven't been able to find a solution that works. Please help!
initializers/apartment.rb
require 'apartment/elevators/host_hash'
config.tenant_names = lambda { Company.pluck :request_host }
Rails.application.config.middleware.use 'Apartment::Elevators::HostHash', Company.full_hosts_hash
initializers/host_hash.rb
require 'apartment/elevators/generic'
module Apartment
module Elevators
class HostHash < Generic
def initialize(app, hash = {}, processor = nil)
super app, processor
#hash = hash
end
def parse_tenant_name(request)
if request.host.split('.').first == "www"
nil
else
raise TenantNotFound,
"Cannot find tenant for host #{request.host}" unless #hash.has_key?(request.host)
#hash[request.host]
end
end
end
end
end
Company Model
after_create :create_tenant
def self.full_hosts_hash
Company.all.inject(Hash.new) do |hash, company|
hash[company.request_host] = company.request_host
hash
end
end
private
def create_tenant
Apartment::Tenant.create(request_host)
end
What ended up working
I changed the elevator configuration to get away from the HostHash one that's in the apartment gem and used a completely custom one. Mostly based off of an issue on the apartment gem github: https://github.com/influitive/apartment/issues/280
initializers/apartment.rb
Rails.application.config.middleware.use 'BaseSite::BaseElevator'
app/middleware/base_site.rb
require 'apartment/elevators/generic'
module BaseSite
class BaseElevator < Apartment::Elevators::Generic
def parse_tenant_name(request)
company = Company.find_by_request_host(request.host)
return company.request_host unless company.nil?
fail StandardError, "No website found at #{request.host} not found"
end
end
end
I think the problem could be that your host_hash.rb lives in the initializers directory. Shouldn't it be in a folder called "middleware"?, as per the Apartment gem ReadME you referenced in your comment. In that example they used app/middleware/my_custom_elevator.rb. Perhaps yours might look like app/middleware/host_hash.rb?
Right now the file is in initializers, so it's loading from there. But your apartment.rb references it by Rails.application.config.middleware.use. Just a hunch but in addition to loading it initially, it may be looking for it in a nonexistent middleware folder. I'd go ahead and create app/middleware, put the file in there instead, and see what happens. Not sure but you might need to alter require paths too.
Let us know if that helps.
UPDATED:
I am setting default scope for some models in a runtime which seems working locally in my development env and my code is given below.
SET_OF_MODELS = [Event, Group, User]
#account = Account.find_by_subdomain(account_subdomain)
SET_OF_MODELS.each { |m| m.set_default_scope(#account.id) }
def set_default_scope(account_id)
default_scope :conditions=> { :account_id => account_id }
end
If I execute this code in ruby console with say #account1, User.first returns #account1 user whereas if I repeat the code with #account2 then User.first returns #account1 user instead of #account2. And this problem is not revealed while running app in local server but in staging server.
My guess is towards their states if they are really cached but not sure. Can someone explain in depth.
Thanks in advance
default_scope will save state in its class. It's harmful in concurrent environment because it leads to race condition. So you must isolate scope state between requests.
You can use around_filter
class ApplicationController < ActionController::Base
around_filter :set_default_scope
def set_default_scope
#account = Account.find_by_subdomain(account_subdomain)
opts = :condition => {:account_id => #account.id}
Event.send(:with_scope, opts) do
Group.send(:with_scope, opts) do
User.send(:with_scope, opts) do
yield
end
end
end
end
end
You can refactor .send(:with_scope, opts) to a class method like with_account_scope(account_id)
Development differs from production. In production all classes are loaded once and cached, so you can't redefine the default scopes on each request.
In development the classes are loaded on each request, to allow easy development: each change you do in the code is visible/active on the next request.
If you really want to, you can disable this behaviour in production. This will make your complete site slower, but maybe that is not really an issue. To turn this off, you have edit your config/environments/production.rb, find the line containing
config.cache_classes = true
and switch that to false.
Hope this helps.
There is nothing wrong with the above code but the problem was with the server used i.e. thin server. It worked perfectly after replacing thin with mongrel. I think thin wasn't allowing to execute set_default_scope more than once except after loading the application.
I am currently using the mixpanel_client gem to access the mixpanel API. I would like to be able to do this in one place:
config = {'api_key' => 'changeme', 'api_secret' => 'changeme'}
client = Mixpanel::Client.new(config)
and then access it anywhere throughout the app. Is there an idiomatic (or framework-matic) way to go about this? It seems like doing this everytime I want to make a request is a waste of resources and not very DRY to boot.
Thanks!
There are a few ways to do it, create an initializer, under initializer folder, so that it will be loaded once after rails is loaded, then
config = {'api_key' => 'changeme', 'api_secret' => 'changeme'}
CLIENT = Mixpanel::Client.new(config)
Then the CLIENT constant will be available anywhere in your app.
Else you can create a class
class MixPanelClient
cattr_accessor: client
def self.client
client ||= begin
config = {'api_key' => 'changeme', 'api_secret' => 'changeme'}
Mixpanel::Client.new(config)
end
end
end
MixPanelClient.client would only create that client once.