I am using the latest versions of both Paperclip (v5.0.0) and Delayed_Paperclip (v3) on Rails 4.2
Everything is working fine except Delayed_Paperclip is not processing the files in the background:
Userfile.rb
class Userfile < ActiveRecord::Base
has_attached_file :userfile,
path: ':get_dir_path/:style_:normalized_file_name',
url: ':get_dir_path/:style_:normalized_file_name',
use_timestamp: false,
styles: lambda { |a| a.instance.check_file_type[:styles] },
only_process: lambda { |a| a.instance.check_file_type[:foreground] },
source_file_options: { all: '-auto-orient' }
validates_attachment_content_type :userfile, content_type: /.*/
process_in_background :userfile,
processing_image_url: lambda { |a|
f = a.instance
f.check_file_type[:processing_image_url].call(f)
},
only_process: lambda { |a| a.instance.check_file_type[:background] }
def check_file_type
if image?
{
styles: {
resized: '800x600',
resized_watermark: {
...
},
thumbnail: {
...
}
},
foreground: [:resized, :resized_watermark, :thumbnail],
background: [],
processing_image_url: lambda { |f| f.actual_path :resized }
}
elsif video?
{
styles: {
screenshot: ['300x300', :jpg],
thumbnail: {
...
},
preview: {
...
},
mp4: {
...
}
},
foreground: [:screenshot, :thumbnail],
background: [:preview, :mp4],
processing_image_url: lambda { |f| f.actual_path :screenshot }
}
elsif audio?
{
styles: {
preview: {
...
},
mp3: {
...
}
},
foreground: [],
background: [:preview, :mp3],
processing_image_url: lambda { |f| f.actual_path }
}
else
{}
end
end
end
Now this all works except for the fact that the ActiveJob that Delayed_Paperclip creates, runs immediately on the same request. Is there something I'm missing to have in process the files after the initial request?
Related
I have set up a multiple media(images) field called pictures on my project content type on Strapi and I have added 2 projects with pictures containing 4 images each.
I want to query these images in Gatsby using Graphql.
This is my plugins array in gatsby-config.js
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`,
},
},
{
resolve: `gatsby-source-strapi`,
options: {
apiURL: `http://localhost:1337`,
queryLimit: 1000,
contentTypes: [`project`],
},
}]
This is my graphql query on localhost:8000/___graphql
query MyQuery {
allStrapiProject {
nodes {
pictures {
formats {
thumbnail {
childImageSharp {
fluid {
src
}
}
}
}
}
}
}
}
This is the result I am getting
{
"data": {
"allStrapiProject": {
"nodes": [
{
"pictures": [
{
"formats": {
"thumbnail": null
}
},
{
"formats": {
"thumbnail": {
"childImageSharp": {
"fluid": {
"src": "/static/eb8a7ee6108ecc0e6185aced82c3316b/b4216/167f320a448c2d6ff65acf179ee627e2.jpg"
}
}
}
}
},
{
"formats": {
"thumbnail": null
}
},
{
"formats": {
"thumbnail": null
}
}
]
},
{
"pictures": [
{
"formats": {
"thumbnail": null
}
},
{
"formats": {
"thumbnail": null
}
},
{
"formats": {
"thumbnail": null
}
},
{
"formats": {
"thumbnail": null
}
}
]
}
]
}
}
}
All of the thumbnails contain null except for one.
I have tried running 'gatsby clean' and sometimes get the query output to have same image urls in multiple places even though i don't have repeating images on Strapi.
As of now, there is no "official" way to make it happen. But there is a workaround which creates a custom node in the build process. For a graphql query like below
query MyQuery {
allStrapiPortfolio {
edges {
node {
category {
images {
localFile {
childImageSharp {
fluid {
base64
tracedSVG
srcWebp
srcSetWebp
originalImg
originalName
}
}
}
}
}
}
}
}
}
The code given below creates the localFile node after images. The code should go in gatsby-node.js.
const { createRemoteFileNode } = require(`gatsby-source-filesystem`);
exports.onCreateNode = async ({ node, actions, store, cache }) => {
const { createNode, createNodeField } = actions;
if (node.internal.type !== null && node.internal.type === "StrapiPortfolio") {
for (const category of node.category) {
for (const image of category.images) {
console.log(image);
const fileNode = await createRemoteFileNode({
url: "http://localhost:1337" + image.url,
store,
cache,
createNode,
createNodeId: (id) => image.id.toString(),
});
if (fileNode) {
image.localFile___NODE = fileNode.id;
}
}
}
}
};
Please note that you will have to customize the code depending on your needs. In my solution, I used two for loops because of my data structure. If you're unsure or just want to check if your custom code works, you can simply add a console.log(node) before the first if statement and a console.log(image) after the second for loop(in my case). That should give you an indication about your data structure and in which way you should proceed.
You need to create a localFile___NODE.
First, you need to edit gatsby-node.js file.
const { createRemoteFileNode } = require(`gatsby-source-filesystem`)
exports.onCreateNode = async ({
node,
actions,
store,
cache,
createNodeId,
}) => {
const { createNode } = actions
// replace ".sliderHome" for the name of multiple media in Strapi CMS
let sliderImages = node.sliderHome
// replace “StrapiHome” for your node type
if (node.internal.type === "StrapiHome") {
if (sliderImages.length > 0) {
// sliderImages.forEach(el => console.log(el))
const images = await Promise.all(
sliderImages.map(el =>
createRemoteFileNode({
url: `http://localhost:1337${el.url}`,
parentNodeId: node.id,
store,
cache,
createNode,
createNodeId,
})
)
)
sliderImages.forEach((image, i) => {
image.localFile___NODE = images[i].id
})
}
}
}
later restart Gatsby and now this is your query
query MyQuery {
allStrapiProject {
nodes {
pictures {
localFile{
childImageSharp{
fluid(maxWidth: 1200){
// or for gatsby use ...GatsbyImageSharpFluid_withWebp
src
}
}
}
}
}
}
}
this has worked for me to bring multiple images with a good quality I hope it works for you
Try below, replace the value you need to display:
Here I am the example for the user avatar
query MyQuery {
allStrapiUser {
edges {
node {
id
avatar {
publicURL
childImageSharp {
fluid {
src
aspectRatio
}
}
}
}
}
}
}
and:
const poster = data.allStrapiUser.edges[0].node
<Img fluid={{aspectRatio: 1.6, src: poster.avatar.publicURL}}/>
I have a page object with this code:
var MyToolComp = require('./MyToolComponent').MyToolComponent;
var MyToolCommand = {
get: function (cssLocator, timeout) {
return new MyToolComp({client: this, locator: cssLocator, timeout: timeout});
},
assertMyToolCount: function (expectedMyToolesCount, timeoutMs) {
console.log('Validate number of MyTool in the page to be [' + expectedMyToolesCount + ']');
this.waitForElementsCount(this.section.john_container.selector, expectedMyToolesCount, timeoutMs);
return this;
},
};
module.exports = {
commands: [MyToolCommand],
sections: {
john_container: {
selector: '.john_container',
elements: {
john_MyTool: {
selector: '.john_MyTool'
},
header: {
selector: '.john_MyTool_header'
}
}
},
multi_widget: {
selector: '.john_multi_widget'
}
}
};
After upgrading to 1.1.0, I can't run this code in my test:
var myToolPage = browser.page.myTool();
myToolPage.assertMyToolCount(1);
When debugging, I see myToolPage has "section" var, but no commands.
If I remove sections and only do
module.exports = MyToolCommand;
or
module.exports = {
commands: [MyToolCommand]
}
Then I can run
myToolPage.assertMyToolCount(1);
But then it failes since
this.section.john_container.selector
Isn't defined.
What am I doing wrong? I can't find anything here or here. Is there anything else I can read that'll help me? What else should I know when upgrading NW? (This is my first time seeing anything nightwatch-related so I kinda have to learn as I go).
Thanks in advance :)
The way I fixed this was replacing
module.exports = {
commands: [MyToolCommand],
sections: {
john_container: {
selector: '.john_container',
elements: {
john_MyTool: {
selector: '.john_MyTool'
},
header: {
selector: '.john_MyTool_header'
}
}
},
multi_widget: {
selector: '.john_multi_widget'
}
}
};
With
exports.commands = MyToolCommand;
exports.sections = {
john_container: {
selector: '.john_container',
elements: {
john_MyTool: {
selector: '.john_MyTool'
},
header: {
selector: '.john_MyTool_header'
}
}
},
multi_widget: {
selector: '.john_multi_widget'
}
};
I have this LinkedHashMap and LinkedHashMap$Entry payload
{box=
{
0={plate=false, id=8269999, knife=1},
1={plate=true, id=8260118, knife=1}
}
}
I want to apply this structure to my Dataweave transformation sample data. I came up with this but the transformation fails during runtime, while preview shows it is a correct transformation...
%dw 1.0
%output application/java
---
[{
"box": {
"0": {
"plate": {
plate: false
} as :object {
class : "java.util.Object"
},
"id": {
id: 8269999
} as :object {
class : "java.util.Object"
},
"knife": {
knife: 1
} as :object {
class : "java.util.Object"
}
} as :object {
class : "java.util.Object"
},
"1": {
"plate": {
plate: true
} as :object {
class : "java.util.Object"
},
"id": {
id: 8260118
} as :object {
class : "java.util.Object"
},
"knife": {
knife: 1
} as :object {
class : "java.util.Object"
}
} as :object {
class : "java.util.Object"
}
} as :object {
class : "java.util.Object"
}
} as :object {
class : "java.util.LinkedHashMap"
}]
java.util.Object doesn't have fields like knife or plate so it would not make any sense to cast to it. What you want is to just remove all the 'as :object...' in your script and DataWeave will return maps as needed.
Also usually the entries in a map (ie LinkedHashMap$Entry) are not referenced directly at all. They are an implementation detail of the specific map implementation.
Just think in terms of the Map interface, not a specific implementation like LinkedHashMap.
angular v 6.1.10
typescript v 2.9.2
rxjs v 6.3.3
ng2-stmompjs v 7.0.0
I am using ng2-stomp library for web sockets which create observable of will initiate a subscription which is observable. In my requirements, I am creating multiple channel subscriptions based on application id and now want to subscribe all these channels all in once or we can say higher order observable so tried to use the various rxjs operator merge, mergeAll, concat but nothing works so far. Here is what I have done so far.
Right now this one is working
appList = [{appID: '123'}, {appID: '345'}];
const appList$ = appList.map((appID: string, idx: number) => {
const headers = Object.assign({}, this.headers, { id: `app_${idx}` });
const watcher = this.rxStompService.watch(`/topic/${appID}`, headers);
console.log({ watcher }); // This is observable
return watcher;
});
appList$.forEach((app$) => {
app$.subscribe((message: Message) => {
const notification: Notification = JSON.parse(message.body);
this.totalNotificationCount++;
if (Object.keys(notification).length) {
this.notificationMessages.push(notification);
}
});
});
{
"watcher": { "_isScalar": false, "source": { "source": { "_isScalar": false } }, "operator": { "connectable": { "source": { "_isScalar": false } } } }
}
BUT I think we can concat all observables in one and can subscribe all. Note that I am unable to use ForkJoin because appList is dynamic and so the number of WebSocket. followings are my trail to convert multiple observable into once.
Trial 1: using concat and map operator
const batch = appList.map((appID, idx) => {
console.log({ appID, idx });
const headers = Object.assign({}, this.headers, { id: `app_${idx}` });
const watcher = this.rxStompService.watch(`/topic/${appID}`, headers);
return watcher;
});
concat(...batch).pipe( map (i => i)).subscribe({ });
this gives error:
Property 'pipe' does not exist on type 'MonoTypeOperatorFunction'.
trial 2: use subscribe all after concat
concat(...batch).subscribe({
next: (v: any) => console.log(v),
complete: () => console.log('Complete')
});
Error: Property 'subscribe' does not exist on type 'MonoTypeOperatorFunction'.
Trail 3: using pipe
const appList$ = appList.map((appID: string, idx: number) => {
const headers = Object.assign({}, this.headers, { id: `app_${idx}` });
const watcher = this.rxStompService.watch(`/topic/${appID}`, headers);
return watcher;
});
console.log({ appList$ });
appList$.pipe(
takeUntil(this.ngUnsubscribe),
tap((i) => {
console.log('tapping', i);
})
);
console.log({appList$}) return this
{
"appList$": [
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
},
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
},
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
},
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
},
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
},
{
"_isScalar": false,
"source": {
"source": {
"_isScalar": false
}
},
"operator": {
"connectable": {
"source": {
"_isScalar": false
}
}
}
}
]
}
Error: Property 'pipe' does not exist on type 'Observable[]'
So my question is how to merge all observable into once and subscribe in once
This is amazing; whenever I write the question here and try again and I found the solution myself.
I have solved this way using from and mergeMap and thanks to this angular in depth article
private watchApplications(appList: string[]) {
const appList$ = from(appList).pipe(
mergeMap((appID, idx) => {
const headers = Object.assign({}, this.headers, { id: `app_${idx}` });
const watcher = this.rxStompService.watch(`/topic/${appID}`, headers);
return watcher;
})
);
appList$
.pipe(
takeUntil(this.ngUnsubscribe),
tap((f: Frame) => {
console.log('tapping Frame', f);
})
)
.subscribe((message: Message) => {
const notification: Notification = JSON.parse(message.body);
console.log({ notification });
this.totalNotificationCount++;
if (Object.keys(notification).length) {
this.notificationMessages.push(notification);
}
});
}
Let's say I have a mapping of how I want a hash to turn out, along with new key names like this:
JSON_MAP = {
image: {
id: :id,
media_url: :url,
time: :duration,
timestamp: :time_posted,
text_caption: :caption,
metadata: {
camera: :camera_type,
flash: :camera_flash
}
},
viewers: {
views: :view_count,
likes: :likes_count
}
}
and I have a hash like this:
{
image: {
id: 1,
media_url: 'http://placekitten.com',
nsfw: false,
time: 4,
timestamp: 14149292,
text_caption: "I'm a kitten",
metadata: {
camera: 'iPhone',
flash: true
}
},
viewers: {
views: 50,
likes: 15
},
extras: {
features: {
enabled: true
}
}
}
I only want it to transform the data so it ends up like:
{
image: {
id: 1,
url: 'http://placekitten.com',
duration: 4,
time_posted: 14149292,
caption: "I'm a kitten",
metadata: {
camera: 'iPhone',
flash: true
}
},
viewers: {
view_count: 50,
likes_count: 15
}
}
Basically, renaming all the keys based on the source map, and deleting any keys that don't match the source map...
You can obtain your desired result using recursion.
def convert(mapper, hsh)
mapper.each_with_object({}) do |(k,o),h|
next unless hsh.key?(k)
if o.is_a? Hash
h[k] = convert(o, hsh[k])
else
h[o] = hsh[k]
end
end
end
Assuming h equals your second hash,
convert(JSON_MAP, h)
#=> { :image=>{
# :id=>1,
# :url=>"http://placekitten.com",
# :duration=>4,
# :time_posted=>14149292,
# :caption=>"I'm a kitten",
# :metadata=>{
# :camera_type=>"iPhone",
# :camera_flash=>true
# }
# },
# :viewers=>{
# :view_count=>50,
# :likes_count=>15
# }
# }