In a Trailblazer operation that uses Paperdragon to process an image, code like this is typical:
def process(params)
validate(params) do |f|
f.image!(f.upload) do |v|
v.process!(:original)
v.process!(:version_a) { |job| job.something... }
v.process!(:version_b) { |job| job.something... }
v.process!(:version_c) { |job| job.something... }
end
end
end
end
which would create four versions from an uploaded image that is accessible as upload on the operation's contract.
I would like to do a few things to :original resulting in :edited and then use :edited as the baseline for :verson_a, :version_b and :version_c. How can I achieve that with Paperdragon?
You can run a second processing block using a version created by the first as its input:
def process(params)
validate(params) do |f|
f.image!(f.upload) do |v|
v.process!(:original)
v.process!(:edited) { |job| job.thumb!(crop) }
end
end
f.image!(f.file(:edited)) do |v|
v.process!(:version_a) { |job| job.something... }
v.process!(:version_b) { |job| job.something... }
v.process!(:version_c) { |job| job.something... }
end
f.save
end
end
where file is a method on the contract:
def file(version)
File.new(Dragonfly.app.datastore.server_root+image[version.to_sym].url)
end
Related
Is there a more efficient way to map a collection of objects to an array of hashes?
def list
#photos = []
current_user.photos.each do |p|
#photos << {
id: p.id, label: p.name, url: p.uploaded_image.url(:small),
size: p.uploaded_image_file_size
}
end
render json: { results: #photos }.to_json
end
This seems a bit verbose but the structure it returns is required by the frontend.
Update
So .map is the preferred method then?
Don't do it in the contoller
Don't generate the json response with map, let's as_json(*) method do that for you.
Don't use # variable in the json render.
Don't use {}.to_json the render json: {} do it under the hood.
in the photo model.
def as_json(*)
super(only: [:id], methods: [:label, :url, :size])
end
alias label name
def size
uploaded_image_file_size
end
def url
uploaded_image.url(:small)
end
controller code.
def list
render json: { results: current_user.photos }
end
Please try
def list
#photos = current_user.photos.map { |p| { id: p.id, label: p.name, url: p.uploaded_image.url(:small), size: p.uploaded_image_file_size } }
render json: { results: #photos }
end
As a starting point, it should be closer to:
def list
#photos = current_user.photos.map do |p|
{
id: p.id, label: p.name, url: p.uploaded_image.url(:small),
size: p.uploaded_image_file_size
}
end
render json: { results: #photos }
end
Just for fun, you could do something like:
class PhotoDecorator < SimpleDelegator
def attributes
%w(id label url size).each_with_object({}) do |attr, hsh|
hsh[attr.to_sym] = send(attr)
end
end
def label
name
end
def url
uploaded_image.url(:small)
end
def size
uploaded_image_file_size
end
end
And then:
> #photos = current_user.photos.map{ |photo| PhotoDecorator.new(photo).attributes }
=> [{:id=>1, :label=>"some name", :url=>"http://www.my_photos.com/123", :size=>"256x256"}, {:id=>2, :label=>"some other name", :url=>"http://www.my_photos/243", :size=>"256x256"}]
You can also do that like this
def array_list
#photos = current_user.photos.map { |p| { id:
p.id, label: p.name, url: p.uploaded_image.url(:small), size: p.uploaded_image_file_size } }
render json: { results: #photos }
end
I have the following job:
class Test::MooJob < ApplicationJob
queue_as :onboarding
def perform
avariable = Test::AragornService.build("a").call
if avariable.status == true
puts "job succeeded"
end
end
end
and the service looks like this:
module Test
class AragornService
def self.build(x)
self.new(x)
end
def initialize(x)
#x = x
end
def call
10.times do
Rails.logger.info #x
end
return ServiceResult.new :status => true, :message => "Service Complete", :data => #x
rescue => e
Bugsnag.notify(e, :context => 'service')
return ServiceResult.new :status => false, :message => "Error occurred - #{e.message}"
end
end
end
I am trying to test it with the following spec:
# bundle exec rspec spec/jobs/test/moo_job_spec.rb
require "rails_helper"
describe Test::MooJob do
subject(:job) { described_class.perform_later }
subject(:job_now) { described_class.perform_now }
let(:key) { "a" }
it 'queues the job' do
ActiveJob::Base.queue_adapter = :test
expect { job }.to have_enqueued_job(described_class)
.on_queue("onboarding")
end
it 'calls the aragorn service once' do
allow(Test::AragornService.new(key)).to receive(:call).and_return(ServiceResult.new(:status => true))
expect_any_instance_of(Test::AragornService).to receive(:call).exactly(1).times
job_now
end
end
Why is it that avariable value keeps returning nil
I get the following error "undefined method `status' for nil:NilClass"
however, when I return a simple boolean,
allow(Test::AragornService.new(key)).to receive(:call).and_return(true)
It sets avariable value to true
here's the ServiceResult class:
class ServiceResult
attr_reader :status, :message, :data, :errors
def initialize(status:, message: nil, data: nil, errors: [])
#status = status
#message = message
#data = data
#errors = errors
end
def success?
status == true
end
def failure?
!success?
end
def has_data?
data.present?
end
def has_errors?
errors.present? && errors.length > 0
end
def to_s
"#{success? ? 'Success!' : 'Failure!'} - #{message} - #{data}"
end
end
Its because you are just setting expections on a unrelated instance of Test::AragornService in your spec:
allow(Test::AragornService.new(key)).to
receive(:call).and_return(ServiceResult.new(:status => true))
This does nothing to effect the instance created by Test::AragornService.build
class Test::MooJob < ApplicationJob
queue_as :onboarding
def perform
avariable = Test::AragornService.build("a").call
if avariable.status == true
puts "job succeeded"
end
end
end
You can solve it by stubbing Test::AragornService.build to return a double:
double = instance_double("Test::AragornService")
allow(double).to receive(:call).and_return(ServiceResult.new(status: true))
# bundle exec rspec spec/jobs/test/moo_job_spec.rb
require "rails_helper"
describe Test::MooJob do
let(:perform_later) { described_class.perform_later }
let(:perform_now ) { described_class.perform_now }
let(:service) { instance_double("Test::AragornService") }
before do
# This injects our double instead when the job calls Test::AragornService.build
allow(Test::AragornService).to receive(:build).and_return(service)
end
it 'queues the job' do
# this should be done in `rails_helper.rb` or `config/environments/test.rb` not in the spec!
ActiveJob::Base.queue_adapter = :test
expect { perform_later }.to have_enqueued_job(described_class)
.on_queue("onboarding")
end
it 'calls the aragorn service once' do
expect(service).to receive(:call).and_return(ServiceResult.new(status: true))
perform_now
end
end
I'm working on a rails application with no models. I have a class in lib, FakeMaker, that builds up a bunch of fake entities for display purposed.
I want to test out deletion functionality but the problem is that my fake data set re-initializes every time I hit the controller.
I'd like to run the data test creator only once so that I only have one set of fake data.
I have tried using ||=, before_filter, class methods in FakeMaker, sessions storage but they all seem to have the issue of reinitializing the data set everytime the controller is hit.
Controller code:
class HomeController < ApplicationController
include FakeMaker
before_filter :set_up_fake_data
def index
#workstations = #data[:workstations]
#data_sources = #data[:data_sources]
end
private
def set_fake_data
#data ||= session[:fake_data]
end
def initialize_data
session[:fake_data] = set_up_fake_data
end
end
FakeMaker in lib:
module FakeMaker
include ActionView::Helpers::NumberHelper
SOURCE_CIDNE = "CIDNE"
SOURCE_DCGS = "DCGS"
TYPES_CIDNE = Faker::Lorem.words(num = 10)
TYPES_DCGS = Faker::Lorem.words(num = 4)
def set_up_fake_data
#data ||= { workstations: fake_maker("workstation", 8), data_sources: fake_maker("data_source", 2) }
end
def fake_maker(type, count)
fake = []
case type
when "report"
count.times { fake << fake_report }
when "workstation"
count.times { fake << fake_workstation }
when "data_source"
fake = fake_data_source
end
fake
end
def fake_report
report = { source: [SOURCE_CIDNE, SOURCE_DCGS].sample,
count: number_with_delimiter(Faker::Number.number(5), :delimiter => ',') }
report[:type] = report[:source] == SOURCE_CIDNE ? TYPES_CIDNE.sample : TYPES_DCGS.sample.capitalize
report
end
def fake_workstation
{ name: Faker::Lorem.word,
downloaded: number_with_delimiter(Faker::Number.number(3), :delimiter => ','),
available: number_with_delimiter(Faker::Number.number(5), :delimiter => ','),
last_connect: fake_time,
queueJSON: fake_queueJSON,
historyJSON: fake_historyJSON }
end
def fake_data_source
data_sources = []
["CIDNE", "DCGS"].each do |source|
data_sources << { type: source,
name: Faker::Internet.url,
status: ["UP", "DOWN"].sample }
end
data_sources
end
def fake_historyJSON
history = []
12.times { history << fake_history }
history
end
def fake_queueJSON
queue = []
35.times { queue << fake_queue }
queue
end
def fake_history
{ sentTimestamp: fake_time,
reportID: Faker::Number.number(5)}
end
def fake_queue
{ priority: Faker::Number.number(3),
queuedTimestamp: fake_time,
reportID: Faker::Number.number(5)}
end
def fake_time
Random.rand(10.weeks).ago.strftime("%Y-%m-%d %H:%M:%S")
end
end
How can I rewrite this code so it's completely dynamic, and I don't have to use the case clause to manually list all possible values of #group?
# Grouping
#group = params[:group] if !params[:group].blank?
case #group
when 'category_id'
#ideas_grouped = #ideas.group_by { |i| i.category_id }
when 'status_id'
#ideas_grouped = #ideas.group_by { |i| i.status_id }
when 'personal_bias'
#ideas_grouped = #ideas.group_by { |i| i.personal_bias }
when 'business_value'
#ideas_grouped = #ideas.group_by { |i| i.business_value }
end
You can use some sort of meta programming
Above code can be refactored in one of the way is
if params[:group].present? && ["category_id","status_id","personal_bias","business_value"].include?(params[:group])
#ideas_grouped = #ideas.group_by { |i| i.send(params[:group]) }
end
If you need no white-listing:
#ideas_grouped = if (group = params[:group]).present?
#ideas.group_by(&group.to_sym)
end
If you need white-listing you may call include? first (see Amar's answer), but to add something new, let me push it with a declarative approach (Object#whitelist is left as an exercise for the reader, maybe comes from Ick):
#ideas_grouped = params[:group].whitelist(IdeaGroupers).maybe do |group|
#ideas.group_by(&group.to_sym)
end
Try out this:
#ideas_grouped = #ideas.group_by { |i| i.send(:"#{#group}")} if (#group = params[:group])
what about :
#group = params[:group] if !params[:group].blank?
#ideas_grouped = ideas_hash.fetch(#group)
def ideas_hash
{
'category_id' => ideas_by_category_id,
'status_id' => ideas_by_status_id,
'personal_bias' => ideas_by_personal_bias
'business_value' => ideas_by_business_value
}
end
def ideas_by_category_id
#ideas.group_by { |i| i.category_id }
end
def ideas_by_status_id
#ideas.group_by { |i| i.status_id }
end
def ideas_by_personal_bias
#ideas.group_by { |i| i.personal_bias }
end
def ideas_by_business_value
#ideas.group_by { |i| i.business_value }
end
I would also make ideas_hash and all of the others method private.
Okay, the last time I touched Ruby is too far back so I cannot give you an example. As far as I understand your problem you are doing a mapping (group -> accessor method) there. So either you use a map object or you build a mapping function using a lambda.
I have a lot of helpers defined which all basically do the same.
def subtitle(page_subtitle)
content_for(:subtitle) { page_subtitle }
end
def header(page_header)
content_for(:header) { page_header }
end
def auto_header(page_auto_header)
content_for(:auto_header) { page_auto_header }
end
def header_image(page_header_image)
content_for(:header_image) { page_header_image }
end
def bodyclass(page_bodyclass)
content_for(:bodyclass) { page_bodyclass }
end
And there are many more...
My question is how can I DRY this code?
I tried something this but I didn't work
content_for_helpers = ["title","subtitle","logocolor"]
content_for_helpers.each do |helper|
def helper(helper)
content_for(helper.parameterize.underscore.to_sym) { helper }
end
end
def helper what
content_for(what) { send "page_#{what}" }
end