Rails checkboxes/form based on YML/DB - ruby-on-rails

I want to create a big checkbox area with maybe about 30 checkboxes. I want, instead of hard-writing each checkbox, to populate this from the db, or from the translation yml. How would I create this loop to do so from a yml? I'm guessing that's bad practice? If so, how to loop for db values?

An example of check boxes of Colors:
From YAML:
# controller
def show
#options = YAML.load(File.read(Rails.root.join('db', 'fixtures', 'checkboxes.yml')))
# view
Check the colors you want:
- #options.each do |option|
= option
= check_box_tag 'colors[]', option
# example of received parameters:
params = { colors: [ 'red', 'blue' ] }
# db/fixtures/checkboxes.yml
---
- Blue
- Red
- White
- Black
- Green
- Yellow
This is a static list of pre-defined Colors which means you would need to manually edit the YML file AND restart your server to see the changes on this list.
Is it a disadvantage ? Yes and no, depends on what you want to do.
From DB:
# controller
def show
#options = Color.all.map{ |color| [color.name, color.id] }
# view
Check the colors you want:
- #options.each do |option_name, value|
= option_name
= check_box_tag 'colors[]', value
# example of received parameters:
params = { colors: [1,2,3] } # ids of Color records
This is a dynamic list of Colors that can be maintained easily via the web interface (no need to restart the server). You can do the basic CRUD (create retrieve update delete) actions on these, whereas the YAML file cannot be handled that easy (possibility to re-write and re-load the file on the fly but WAOW such a pain in the ass in comparison to the "Rails' way" with a Color model.
loaded from I18n's translation file:
# implying that en.posts.colors is a list of color names
en:
# ...
posts:
# ...
colors:
black: Black
red: Red
white: White
# view
- t('posts.colors').each do |i18n_key, color_name|
= color_name
= check_box_tag 'colors[]', color_name
# example of received parameters:
params = { colors: [ 'red', 'blue' ] }
How to decide?
It depends on what you need:
You need the list to be static? (classic usage: countries, languages, colors, genders, etc.) -> Go for the YAML option
You need the list to be dynamic? (classic usage: users, categories, content that can be edited inside the app) -> Go for the Model option

Related

How to examine and change actions on commercetools updateCart query?

My question is this:
Is there a feature or method within graphql that allows editing of incoming queries EASILY so that I can prevent an action from being performed? I would like to examine my query in a web proxy, edit the actions, and then send it on to its destination.
Context:
I have a mutation running in a proxy service for commercetools that looks like this:
mutation AddLineItem(
$id: String
$version: Long!
$sku: String
$quantity: Long
$currencyCode: Currency!
$centAmount: Long!
$custom: String!
) {
updateCart(
version: $version
id: $id
actions: [
{
addLineItem: {
sku: $sku
quantity: $quantity
externalPrice: { centPrecision: { currencyCode: $currencyCode, centAmount: $centAmount } }
custom: { typeKey: "custom-type", fields: [{ name: "field", value: $custom }] }
}
}
]
) {
id
version
lineItems {
...LineItemFieldsCart
}
}
}
According to the commercetools documentation, when the external price field is set, duplicate items will be added as separate line items to the cart instead of increasing the quantity, as is the default behavior. The thing is, I want the default behavior instead of the duplicate line items. The normal way to mitigate this is to add an update extension to delete the duplicates, but I am working on an already mature system that has multiple update extensions that all operate on the line items, making a delete apparatus a heavy lift in the update extension. I want to edit the query in the proxy so the duplicate item never happens in the first place. Is there an easy way to do this?
for the add line item update action the current behaviour is to create a separate line item when external prices are used and not add the additional quantity to the already existing line item as you described.
If you want to make sure that the quantity is added to the already existing line item when using external prices, you could do a check prior to adding a new line item and use the update action change line item quantity in case the product variants match with the line items that already exist in the cart. This will let you set the external price there and will not add any additional like items. https://docs.commercetools.com/api/projects/carts#change-lineitem-quantity

Wagtail 2.12 translations problem with wagtail_localize and StreamField

I am using wagtail built-in translations system with wagtail_localize.
For StreamFields with nested content, only external content is available for translation.
Look at this example:
class Carousel(blocks.StructBlock):
title = blocks.CharBlock(label='Titolo')
text = blocks.RichTextBlock(label='Testo')
items = blocks.ListBlock(blocks.StructBlock([
('tab_label', blocks.CharBlock(label='Testo visualizzato sulla tab')),
('title', blocks.CharBlock(label='Titolo')),
('text', blocks.RichTextBlock(label='Testo')),
('photo', ImageChooserBlock(label='Foto')),
('page', QuickPageLinkBlock(required=False)),
('color', NativeColorBlock(default="#085083", label = 'Colore per il titolo')),
]), label = 'Lista slide')
class Meta:
icon='snippet'
label = 'Carosello'
template = 'blocks/carousel.html'
Only title and text are available for translations, but not all the items.
I also need to translate items. How can i solve?
Moreover it seems that linked pages contained in snippets (in pages it works) cannot be translated, but I need to change the link to the correct language page.
Can someone help me?
Thanks,
Sabrina
I recommend using a StreamBlock instead of ListBlock for items.
The content of StreamBlock is translatable. I don't think ListBlocks can be translated yet (see here)
# Do not do this:
items = blocks.ListBlock(blocks.StructBlock(...))
# Do this instead:
items = blocks.StreamBlock([('item', blocks.StructBlock(...))])

Wagtail Draftail: Add class to div

I want to add a custom element to my draftail editor. Adding a div element was not a problem. But I also want to add to the div my own class.
How can I do this? This is my current code:
#hooks.register('register_rich_text_features')
def register_infobox_feature(features):
"""
Registering the `mark` feature, which uses the `MARK` Draft.js inline style type,
and is stored as HTML with a `<mark>` tag.
"""
feature_name = 'infobox'
type_ = 'div'
tag = 'div'
# 2. Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': 'InfoBox',
'description': 'Infobox',
# This isn’t even required – Draftail has predefined styles for MARK.
# 'style': {'textDecoration': 'line-through'},
}
# 3. Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
)
# 4.configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {tag: InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: tag, 'props': {'class' : 'test'}}},
}
# 5. Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule('contentstate', feature_name, db_conversion)
# 6. (optional) Add the feature to the default features list to make it available
# on rich text fields that do not specify an explicit 'features' list
features.default_features.append('infobox')
It looks like that it is possible for new block elements but not for entities. Why?
https://docs.wagtail.io/en/v2.6/advanced_topics/customisation/extending_draftail.html#creating-new-inline-styles
Had the same problem today, I could not find solution in any documentation, but I simply added:
tag = 'div class="myClass"'
and it worked.
I am posting whole funtion I have just in case someone would look for such solution:
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import InlineStyleElementHandler
from wagtail.core import hooks
# 1. Use the register_rich_text_features hook.
#hooks.register('register_rich_text_features')
def register_flashgreen_feature(features):
"""
Registering the `mark` feature, which uses the `MARK` Draft.js inline style type,
and is stored as HTML with a `<mark>` tag.
"""
feature_name = 'FlashGreen'
type_ = 'FG'
tag = 'div class="FG"'
# 2. Configure how Draftail handles the feature in its toolbar.
control = {
'type': type_,
'label': 'Flash_Green',
'description': 'Flash Green',
'style': {'color': '#00F000'},
}
# 3. Call register_editor_plugin to register the configuration for Draftail.
features.register_editor_plugin(
'draftail', feature_name, draftail_features.InlineStyleFeature(control)
)
# 4.configure the content transform from the DB to the editor and back.
db_conversion = {
'from_database_format': {tag: InlineStyleElementHandler(type_)},
'to_database_format': {'style_map': {type_: tag}},
}
# 5. Call register_converter_rule to register the content transformation conversion.
features.register_converter_rule(
'contentstate', feature_name, db_conversion)
# 6. (optional) Add the feature to the default features list to make it available
# on rich text fields that do not specify an explicit 'features' list
features.default_features.append('FlashGreen')

How can I set Algolia searchableAttributes on an instantsearch widget?

I am trying to set searchable attributes so that these can by dynamically controlled by locale. I am attempting to follow this guide from algolia on multi lang support:
https://www.algolia.com/doc/guides/search/multilingual-search/
The example shows setting this value on index:
Algolia.init_index('movies').set_settings({"searchableAttributes"=>["title_eng,title_fr,title_es"]})
but this is not how I am creating my index, maybe I am missing something? I also don't appear to have the set_settings method on the helper.
I am trying to set this via:
helper.setQueryParameter('searchableAttributes', searchable_terms_array)
found towards the bottom of the following coffee script code block
searchable_terms_array = [
'title_de'
'title_en'
'title_fr'
]
restricted_terms_array = [
'title_' + current_locale
]
search = instantsearch(
appId: 'MY-ID'
apiKey: 'MY_KEY'
indexName: 'my_index_' + rails_env
urlSync: {
threshold: 300
getHistoryState: ->
{ turbolinks: true }
}
searchFunction: (helper) ->
query = search.helper.state.query
# Here is my attempt, doesn't seem to work
helper.setQueryParameter('searchableAttributes', searchable_terms_array)
# is there another way to set above line?
helper.setQueryParameter('restrictSearchableAttributes', restricted_terms_array)
videos.helper.setQuery query
videos.helper.search()
helper.search()
return
)
Finally, it may be important to note that I am setting the primary searchable attributes via the Algolia admin console, but assume I am supposed to be setting the additional language related fields to searchable via the API.
searchableAttributes is setting not a query parameter. The JS Helper is a query only layer on top of the client. This means that you can't set the settings of your index using the JS Helper.
What you need to do for multiple language support is to create a replicas per language. Each replica will have a different set of searchable attributes. Then using instantsearch.js or the JS Helper you can switch indices, respectively using the sortBySelector widget or the setIndex method of the helper.

Rails Microsoft Word, XML databinding, repeat rows

Those willing to jump straight to my questions can go to the paragraph "Please help with". You will find there my beginning of implementation, along with short XML samples
The story
The famous problem of inserting repeating content, like table rows, into a word template, using the rails framework.
I decided to implement a 'cleaner' solution for replacing some variables in a Word document with rails, using XML databinding. This solution works very well for non-repetitive content, but for repetitive content, a little extra dirty work must be done and I need help with it.
No C#, No Visual, just plain olde ruby on rails & XML
The databinded document
I have a Word document with some content controls, tagged with "human-readable" text, so my users know what should be inside.
I have used Word 2007 Content Control Toolkit to add some custom XML to a .docx file. Therefore in each .docx I have some customXml/itemsx.xml that contains my custom XML.
I have manually databinded this XML to text content control I have in my word template, using drag & drop with Word 2007 Content Control Toolkit.
The replacing process with nokogiri
Basically I already have some code that replaces every XML node by the corresponding value from a hash. For example if I provide this hash to my function :
variables = {
"some_xml-node" => "some_value"
}
It will properly replace XML in customXml/itemsx.xml of .docx file :
<root> <some> <xml-node>some_value</xml-node></some> </root>
So this is taken care of !
The repetitive content
Now as I said, this works perfectly for non-repetitive content. For repetitive content (in my case I want to repeat some <w:tr> in a document), the solution I'd like to go with, is
Manually insert some tags in word/document.xml of .docx file (this is dirty, but hell I can't think of anything else) before every <tr> that needs to be duplicated
In rails, parse the XML and locate the <tr> that needs duplicating using Nokogiri
Copy the tr as many times as I need
Look at some text inside this <tr>, find the databinding (which looks like <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]"
Replace movie[1] by movie[index]
Repeat for every table that needs <tr> duplication
With this solution Therefore I ensure 100% compatibility with my existing system ! It's some kind of preprocessing...
Please help with
Finding an XML comment containing a custom string, and selecting the node just below it (using Nokogiri)
Changing attributes in many sub-nodes of the node found in 1.
XML/Hash samples that could be used (my beginning of implementation after that):
Sample of .docx word/document.xml
<w:document>
<!-- My_Custom_Tag_ID -->
<w:tr someparam="something">
<w:td></w:td>
<w:td><w:sthelse></w:sthelse><w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]><w:sth>Value</w:sth></w:td>
<w:td></<:td>
</w:tr>
</w:document>
Sample of input parameter repeat_tag hash
repeat_tags_sample = [
{
"tag" => "My_Custom_Tag_ID",
"repeatable-content" => "movie"
},
{
"tag" => "My_Custom_Tag_ID_2",
"repeatable-content" => "cartoons"
}
]
Sample of input parameter contents hash
contents_sample =
{
"movies" => [{"name" => "X-Men",
"year" => 1998,
"property-xxx" => 42
}, { "name" => "X-Men-4",
"year" => 2007,
"property-xxx" => 42
}],
"cartoons" => [{"name" => "Tom_Jerry",
"year" => 1995,
"property-yyy" => "cat"
}, { "name" => "Random_name",
"year" => 2008,
"property-yyy" => 42
}]
}
My beginning of implementation :
def dynamic_table_content(zip, repeat_tags, contents)
doc = zip.find_entry("word/document.xml")
xml = Nokogiri::XML.parse(doc.get_input_dtream)
# repeat_tags_sample = [ {
# "tag" => My_Custom_Tag_ID",
# "repeatable-content" => "movie"},
# ...]
repeat_tags.each do |rpt|
content = contents[rpt[:repeatable-content]]
# content now looks like [
# {"name" => "X-Men",
# "year" => 1998,
# "property-xxx" => 42, ...},
# ...]
content_name = rpt[:repeateable_content].to_s
# the 'movie' of '/root[1]/movies[1]/movie[1]/name[1]' (see below)
puts "Processing #{rpt[:tag]}, adding #{content_name}s"
# Word document.xml sample code looks like this :
# <!-- My_Custom_Tag_ID_inserted_manually -->
# <w:tr ...>
# ...
# <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]>
# ...
# </w:tr>
Find a comment containing a custom string, and select the node just below
# Find starting <w:tr > tag located after <!-- rpt[:tag] -->
base_tr_node = find the node after
# Duplicate it as many times as we want.
content.each_with_index do |content, index|
puts "Adding #{content_name} : #{content}.to_s"
new_tr_node = base_tr_node.add_next_sibling(base_tr_node)
# inside this new node there are many
# <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]>
# <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/year[1]>
# ..../movie[1]/property-xxx[1]
# GOAL : replace every movie[1] by movie[index]
Change attributes in many sub-nodes of the node found in 1.
new_tr_node.change_attributes as shown in (see GOAL in previous comments)
# Maybe, it would be something like
# new_tr_node.gsub("(#{content_name})\[([1-9]+)\]", "\1\[#{index}\]")
# ... But new_tr_node is a nokogiri element so .gsub doesn't exist
end
end
#replace["word/document.xml"] = xml.serialize :save_zip_with => 0
end
I have looked at the DoPE extension for Word documents. It looks great ! But alas I had already done a lot of work, and just now I (almost) finished building my own preprocessor.
What I needed was more complicated than what I originally asked. But nevertheless, the answers would be :
EDIT : fixed bad regex/xpath
# 1. Find a comment containing a custom string, and select the node just below
comment_nodes = doc.xpath("//comment()")
# Loop like comment_nodes.each do |comment|
base_tr_node = comment.next_sibling.next_sibling
# For some reason, need to apply next_sibling twice, thought the comment is indeed just above the <w:tr> node
# 2. Change attributes in many sub-nodes of the node found in 1.
matches = tr_node.search('.//*[name()='w:dataBinding']')
matches.each do |databinding_node|
# replace '.*phase[1].*' by '.*phase[index].*'
databinding_node['w:xpath'].gsub("#{comment.text}\[1\]", "#{comment.text}\[#{index}\]")
end

Resources