I have the following contract and I would like to somehow set apikey to default to whatever is set in ENV.fetch('MY_ENV') so that users don't have to pass apikey param to every contract. I'd like it be injected automatically somehow if possible.
require 'dry-validation'
class BaseContract < Dry::Validation::Contract
params do
required(:apikey).value(:string)
end
rule(:apikey) do
key.failure("must provide 'apikey'. Please set 'TICKETMASTER_API_KEY' in your environment")
end
end
class Event < BaseContract
params do
required(:id).value(:string)
end
end
event = Event.new
event.call(id: '123') # <= I'd like :apikey to be automatically set here
Is it possible?
Related
I am using the iex exchange api for grabbing info about stocks. It's working great but my code is very ugly was wondering how to refactor the code.
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
This is what is needed to grab the info. The problem is that I have to put the code above in every method that utilizes the api. For instance,
def self.stock_price(ticker)
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
company = stock.company(ticker)
quote = stock.quote(ticker.upcase)
puts "#{company.company_name}: #{quote.latest_price}"
end
def self.week_52_high(ticker)
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
key_stats = stock.key_stats(ticker)
puts "52-week high: #{key_stats.week_52_high}"
end
def self.week_52_low(ticker)
stock = IEX::Api::Client.new(
publishable_token: token,
endpoint: 'https://sandbox.iexapis.com/v1'
)
key_stats = stock.key_stats(ticker)
puts "52-week low: #{key_stats.week_52_low}"
end
Is there any way to factor that call out to a different file and call the method that way? The code is very repetitive as is. The "stock" variable is what i need to actually work with, should I made that a global variable? I've heard that's a no-no but is this case an exception? Also, where i have
publishable_token: token,
that token variable is actually my actual, hard-coded token, not the "token" variable you see, I simply changed it for security issues. What should I do instead of hard-coding it? The documentation says to turn it into an environment variable but i dont know what that means. Thanks in advance!
What should I do instead of hard-coding it? The documentation says to
turn it into an environment variable but i dont know what that means.
An environment variable is a variable whose value is set outside the the application, typically through functionality built into the operating system or shell. You need to check the documentation for your setup to see how to set env vars.
You can get env vars in Ruby through the ENV hash.
ENV['FOO']
ENV.fetch('FOO') # will raise a KeyError if it is not set instead of just returning nil
Rails 5.2 and up have secure credentials that can be used instead. It stores your credentials in an encrypted YAML file that can be checked into source control.
How do I refactor this?
One way to refactor this would be to use delegation instead of bunch of largely static methods:
require 'forwardable'
class MyClient
extend Forwardable
TOKEN = ENV.fetch('IEX_API_TOKEN')
ENDPOINT = ENV.fetch('IEX_API_ENDPOINT', 'https://sandbox.iexapis.com/v1')
def_delegators :#client, :company, :quote, :key_stats
def initialize(publishable_token: TOKEN, endpoint: ENDPOINT, client: nil)
# This is know as constructor injection and makes it easy to mock out
# the dependency in tests
#client = client || IEX::Api::Client.new(publishable_token: TOKEN, endpoint: ENDPOINT)
end
def stock_price(ticker)
company_name = company(ticker).company_name
price = quote(ticker.upcase).latest_price
puts "#{company_name}: #{price}"
end
def week_52_high(ticker)
puts "52-week high: #{key_stats(ticker).week_52_high}"
end
def week_52_low(ticker)
puts "52-week low: #{key_stats(ticker).week_52_low}"
end
end
#client = MyClient.new
#client.week_52_low(ticker)
I am trying to pass extra params inside the options{} hash in the confirmation email but It is just showing me subject and from headers in the mailer.
This is my code
CustomMailer.confirmation_instructions(user,token, {custom_param: "abc"})
When I show opts data inside template like this
#first_name = opts
It shows
{:subject=>"Email Confirmation", :from=>"no-reply#sample.com"}
custom mailer code is
class CustomMailer < Devise::Mailer
helper :application # gives access to all helpers defined within `application_helper`.
include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
#include ApplicationHelper
default template_path: 'devise/mailer' # to make sure that you mailer uses the devise views
def confirmation_instructions(record, token, opts={})
opts[:subject] = "Email Confirmation"
opts[:from] = 'no-reply#sample.com'
if(record["admin"]==false)
#template_type = 'donor'
#first_name = opts
end
end
why it is not working?
Not a complete answer but the comments section was annoying me, try:
def confirmation_instructions(record, token, opts={})
#opts = opts
opts[:subject] = "Email Confirmation"
opts[:from] = 'no-reply#sample.com'
if(record["admin"]==false)
#template_type = 'donor'
#first_name = opts
end
end
Then in your mailer, where you were calling #first_name, call #opts and it should give you the argument you feed into the method call.
I would like to write a custom validator for a given validates call:
class Worker
include ActiveModel::Validations
def initialize(graph_object)
#graph_object = graph_object
end
attr_accessor :graph_object
validates :graph_object, graph_object_type: {inclusion: [:ready, :active]}
end
class GraphObject
attr_accessor :state
end
I would like to validate Worker#graph_object based on a GraphObject#state. So the Worker is valid when the passed in GrapObject is in a :ready or :active state. I would like to reuse as much of the ActiveModel as possible.
Validations documentation describes the process of setting up the custom validator but I can't figure out how to do it.
I think I have to start with this:
class GraphObjectTypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
end
end
options[:inclusion] = [:ready, :active]
record is the instance of the Worker(i think...)
value I have no idea (is value = record.graph_object ?)
attribute same as for value - no idea
Maybe validates :graph_object, graph_object_type: {inclusion: [:ready, :active]} isn't defined right?
OK I think I figured it out - I love puts debugging! Who needs pry!
One way of doing it would be:
class GraphObjectTypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if options.key?(:inclusion) && not_included?(value.type)
record.errors.add(attribute, "wrong graph object type")
end
end
private
def not_included?(type)
!options[:inclusion].include?(type)
end
end
options[:inclusion]: [:ready, :active] array
record: instance of the Worker
value: instance of the GraphObject
attribute: :graph_object symbol
Consider I have this following model definition, I want a particular property which should be constant from the moment it has been created
class A
property :a1, String, :freeze => true
end
Is there something like this? or may be using callbacks ?
Try the following:
class YourModel
property :a1, String
def a1=(other)
if a1
raise "A1 is allready bound to a value"
end
attribute_set(:a1, other.dup.freeze)
end
end
The initializer internally delegates to normal attribute writers, so when you initialize the attribute via YourModel.new(:a1 => "Your value") you cannot change it with your_instance.a1 = "your value".. But when you create a fresh instance. instance = YourModel.new you can assign once instance.a1 = "Your Value".
If you don't need to assign the constant, then
property :a1, String, :writer => :private
before :create do
attribute_set :a1, 'some value available at creation time'
end
may suffice
Say for example I have two classes within my Rails application - Customer class and a Card class. The Customer class is composed of a Card class i.e. the customer has a card.
I then have a Rails controller with a 'do_something' action defined, which will initialise a new instance of Customer (which in-turn will internally create a new instance of Card) using the params passed in on the POST.
The number of the card is then set as follows:
class ShopController < ApplicationController
def do_something
customer = Customer.new params
customer.card.number = params[:card_number]
...
end
end
How is this assignment of the card number tested in an RSpec test? Ideally, if 'should_receive_chain' existed we could write:
describe MyController do
describe "POST 'do_something'" do
it "should set card number"
params = { :card_number => '1234' }
card_mock.should_receive_chain(:card, :number).with '1234'
post :do_something
end
end
end
Any ideas? Perhaps the fact that it can't be tested easily is a code smell, and maybe I should create a setter method on the Customer class?
I do think you are testing this at the wrong level. If you are wed to setting the card number in a separate statement then it might be better to create a function to help with this.
class Customer
def self.new_with_card_number(params, number)
customer = new(params)
customer.card.number = number
customer
end
end
describe Customer do
it 'creates a card with a number' do
customer = described_class.new_with_card_number({}, '1234')
customer.card.number.should == '1234'
end
end
You could then change your controller to:
class ShopController < ApplicationController
def do_something
customer = Customer.new_with_card_number(params, params[:card_number])
end
end
A simpler solution might be to simply name the parameter in your form so that setting the card number is done automatically:
params[:customer][:card_attributes][:card_number]
You could then just change the call to customer = Customer.new(params).
I would change the Customer model to add accepted_nested_attributes_for :card and change the controller action to
class ShopController < ApplicationController
def do_something
customer = Customer.create params[:customer]
...
end
end
then your spec can look like
describe ShopController do
describe 'POST /do_something' do
it "sets the card's card number" do
post :do_something, :customer =>
{
:card_attributes => {:card_number => '1234'}
}
Customer.last.card.number.should == '1234'
end
end
end