我正在构建 PWA 并使用 Ramda 进行逻辑构建。我正在尝试构建一个函数,该函数给出了 Google Places Detail response返回一个自定义地址对象。
让我通过向您展示我的测试来在代码中描述它:
assert({
given: 'a google places api response from Google Places',
should: 'extract the address',
actual: getAddressValues({
address_components: [
{
long_name: '5',
short_name: '5',
types: ['floor'],
},
{
long_name: '48',
short_name: '48',
types: ['street_number'],
},
{
long_name: 'Pirrama Road',
short_name: 'Pirrama Rd',
types: ['route'],
},
{
long_name: 'Pyrmont',
short_name: 'Pyrmont',
types: ['locality', 'political'],
},
{
long_name: 'Council of the City of Sydney',
short_name: 'Sydney',
types: ['administrative_area_level_2', 'political'],
},
{
long_name: 'New South Wales',
short_name: 'NSW',
types: ['administrative_area_level_1', 'political'],
},
{
long_name: 'Australia',
short_name: 'AU',
types: ['country', 'political'],
},
{
long_name: '2009',
short_name: '2009',
types: ['postal_code'],
},
],
geometry: {
location: {
lat: -33.866651,
lng: 151.195827,
},
viewport: {
northeast: {
lat: -33.8653881697085,
lng: 151.1969739802915,
},
southwest: {
lat: -33.86808613029149,
lng: 151.1942760197085,
},
},
},
}),
expected: {
latitude: -33.866651,
longitude: 151.195827,
city: 'Pyrmont',
zipCode: '2009',
streetName: 'Pirrama Road',
streetNumber: '48',
},
});
evolve 来做这件事,但这会保留 key 。我需要先使用进化来转换对象,然后 reduce传播 key 的对象。// Pseudo
({ address_components }) => ({ ...address_components })
evolve成功提取了相关信息并使用 renameKeys 重命名 key 来自 Ramda adjunct,但我不知道之后如何压平该对象。你是怎样做的?或者是否有更简单的方法来实现所需的转换?export const getAddressValues = pipe(
evolve({
address_components: pipe(
reduce(
(acc, val) => ({
...acc,
...{
[head(prop('types', val))]: prop('long_name', val),
},
}),
{}
),
pipe(
pickAll([
'route',
'locality',
'street_number',
'country',
'postal_code',
]),
renameKeys({
route: 'streetName',
locality: 'city',
street_number: 'streetNumber',
postal_code: 'zipCode',
}),
map(ifElse(isNil, always(null), identity))
)
),
geometry: ({ location: { lat, lon } }) => ({
latitude: lat,
longitude: lon,
}),
}),
({ address_components, geometry }) => ({ ...address_components, ...geometry })
);
const getLongNameByType = (arr, type) =>
arr.find(o => o.types.includes(type)).long_name;
const getAddressValues = ({ address_components: comp, geometry: { location: { lat, lng } } }) => ({
latitude: lat,
longitude: lng,
city: getLongNameByType(comp, 'locality'),
zipCode: getLongNameByType(comp, 'postal_code'),
streetName: getLongNameByType(comp, 'route'),
streetNumber: getLongNameByType(comp, 'street_number'),
country: getLongNameByType(comp, 'country'),
});
最佳答案
镜头可能是您最好的选择。 Ramda 有一个通用的 lens 函数,以及对象属性( lensProp )、数组索引( lensIndex )和更深路径( lensPath )的特定函数,但不包括在数组中查找匹配值的函数通过身份证。不过,我们自己制作并不难。
通过将两个函数传递给 lens 来制作镜头:获取对象并返回相应值的getter,以及获取新值和对象并返回对象的更新版本的setter。
我们写在这里lensMatch它查找或设置数组中给定属性名称与提供的值匹配的值。和 lensType简单地通过 'type'至 lensMatch取回一个函数,该函数将采用一组类型并返回一个镜头。
使用任何镜头,我们都有 view , set , 和 over 分别获取、设置和更新值的函数。
const lensMatch = (propName) => (key) => lens (
find ( propEq (propName, key) ),
(val, arr, idx = findIndex (propEq (propName, key), arr)) =>
update(idx > -1 ? idx : length(arr), val, arr)
)
const lensTypes = lensMatch ('types')
const longName = (types) =>
compose (lensProp ('address_components'), lensTypes (types), lensProp ('long_name'))
// can define `shortName` similarly if needed
const getAddressValues = applySpec ( {
latitude: view (lensPath (['geometry', 'location', 'lat']) ),
longitude: view (lensPath (['geometry', 'location', 'lng']) ),
city: view (longName (['locality', 'political']) ),
zipCode: view (longName (['postal_code']) ),
streetName: view (longName (['route']) ),
streetNumber: view (longName (['street_number']) ),
})
const response = {"address_components": [{"long_name": "5", "short_name": "5", "types": ["floor"]}, {"long_name": "48", "short_name": "48", "types": ["street_number"]}, {"long_name": "Pirrama Road", "short_name": "Pirrama Rd", "types": ["route"]}, {"long_name": "Pyrmont", "short_name": "Pyrmont", "types": ["locality", "political"]}, {"long_name": "Council of the City of Sydney", "short_name": "Sydney", "types": ["administrative_area_level_2", "political"]}, {"long_name": "New South Wales", "short_name": "NSW", "types": ["administrative_area_level_1", "political"]}, {"long_name": "Australia", "short_name": "AU", "types": ["country", "political"]}, {"long_name": "2009", "short_name": "2009", "types": ["postal_code"]}], "geometry": {"location": {"lat": -33.866651, "lng": 151.195827}, "viewport": {"northeast": {"lat": -33.8653881697085, "lng": 151.1969739802915}, "southwest": {"lat": -33.86808613029149, "lng": 151.1942760197085}}}}
console .log (
getAddressValues (response)
) <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script><script>
const {applySpec, compose, find, findIndex, lens, lensProp, lensPath, propEq, update, view} = R </script>lensMatch 版本。对于这个问题,因为我们没有使用 setter:const lensMatch = (propName) => (key) =>
lens (find (propEq (propName, key) ), () => {} )
lensMatch是一个有用的效用函数。view内longName并编写另一个小助手来包装 lensPath 的结果在 view简化调用看起来更像这样。 longitude: viewPath (['geometry', 'location', 'lng']),
city: longName (['locality', 'political']),
applySpec ,也许 viewSpec它简单地将所有属性函数包装在 view 中.这些留给读者作为练习。const makeKey = JSON.stringify
const matchType = (name) => (
spec,
desc = spec.reduce( (a, [t, n]) => ({...a, [makeKey (t)]: n}), {})
) => (xs) => xs.reduce(
(a, { [name]: fld, types }, _, __, k = makeKey(types)) => ({
...a,
...(k in desc ? {[desc[k]]: fld} : {})
}),
{}
)
const matchLongNames = matchType('long_name')
const getAddressValues2 = lift (merge) (
pipe (
prop ('address_components'),
matchLongNames ([
[['locality', 'political'], 'city'],
[['postal_code'], 'zipCode'],
[['route'], 'streetName'],
[['street_number'], 'streetNumber'],
])
),
applySpec ({
latitude: path(['geometry', 'location', 'lat']),
longitude: path(['geometry', 'location', 'lng']),
})
)
const response = {"address_components": [{"long_name": "5", "short_name": "5", "types": ["floor"]}, {"long_name": "48", "short_name": "48", "types": ["street_number"]}, {"long_name": "Pirrama Road", "short_name": "Pirrama Rd", "types": ["route"]}, {"long_name": "Pyrmont", "short_name": "Pyrmont", "types": ["locality", "political"]}, {"long_name": "Council of the City of Sydney", "short_name": "Sydney", "types": ["administrative_area_level_2", "political"]}, {"long_name": "New South Wales", "short_name": "NSW", "types": ["administrative_area_level_1", "political"]}, {"long_name": "Australia", "short_name": "AU", "types": ["country", "political"]}, {"long_name": "2009", "short_name": "2009", "types": ["postal_code"]}], "geometry": {"location": {"lat": -33.866651, "lng": 151.195827}, "viewport": {"northeast": {"lat": -33.8653881697085, "lng": 151.1969739802915}, "southwest": {"lat": -33.86808613029149, "lng": 151.1942760197085}}}}
console .log (
getAddressValues2 (response)
) <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script><script>
const {applySpec, lift, merge, path, pipe, prop} = R </script>latitude和 longitude ,一个用于其他更难匹配的,然后简单地合并将每个应用到响应的结果。applySpec 的简单应用和 path .另一个,封装为 matchType接受将输入的类型(以及要提取的字段的名称)与输出的属性名称匹配的规范。它建立了一个索引,desc ,基于类型(这里使用 JSON.stringify ,虽然有明显的替代品)。然后它减少一个对象数组,找到任何其 types 的对象。属性在索引中并将其值与适当的字段名称联系起来。Maybe 有很多话要说在这里。但是我可能希望以两种不同的方式与结果进行交互。一个让我逐个字段操作,每个字段都包裹在自己的 Maybe 中.另一个是作为一个完整的对象,拥有它的所有领域;但出于所展示的原因,它必须包含在自己的 Maybe 中。const maybeObj = pipe (
toPairs,
map(([k, v]) => v.isJust ? Just([k, v.value]) : Nothing()),
sequence(Maybe),
map(fromPairs)
)
const maybeSpec = (spec = {}) => (obj = {}) =>
Object .entries (spec) .reduce (
(a, [k, f] ) => ({...a, [k]: Maybe (is (Function, f) && f(obj))}),
{}
)
const findByTypes = (types = []) => (xs = []) =>
xs .find (x => equals (x.types, types) )
const getByTypes = (name) => (types) => pipe (
findByTypes (types),
prop (name)
)
const getAddressComponent = (types) => pipe (
prop ('address_components'),
getByTypes ('long_name') (types)
)
const response = {"address_components": [{"long_name": "5", "short_name": "5", "types": ["floor"]}, {"long_name": "48", "short_name": "48", "types": ["street_number"]}, {"long_name": "Pirrama Road", "short_name": "Pirrama Rd", "types": ["route"]}, {"long_name": "Pyrmont", "short_name": "Pyrmont", "types": ["locality", "political"]}, {"long_name": "Council of the City of Sydney", "short_name": "Sydney", "types": ["administrative_area_level_2", "political"]}, {"long_name": "New South Wales", "short_name": "NSW", "types": ["administrative_area_level_1", "political"]}, {"long_name": "Australia", "short_name": "AU", "types": ["country", "political"]}, {"long_name": "2009", "short_name": "2009", "types": ["postal_code"]}], "geometry": {"location": {"lat": -33.866651, "lng": 151.195827}, "viewport": {"northeast": {"lat": -33.8653881697085, "lng": 151.1969739802915}, "southwest": {"lat": -33.86808613029149, "lng": 151.1942760197085}}}}
getAddressComponent (['route']) (response)
const extractAddress = maybeSpec({
latitude: path (['geometry', 'location', 'lat']),
longitude: path (['geometry', 'location', 'lng']),
city: getAddressComponent (['locality', 'political']),
zipCode: getAddressComponent (['postal_code']),
streetName: getAddressComponent (['route']),
streetNumber: getAddressComponent (['street_number']),
})
const transformed = extractAddress (response)
// const log = pipe (toString, console.log)
const log1 = (obj) => console.log(map(toString, obj))
const log2 = pipe (toString, console.log)
// First variation
log1 (
transformed
)
// Second variation
log2 (
maybeObj (transformed)
) <script src="https://bundle.run/ramda@0.26.1"></script>
<script src="https://bundle.run/ramda-fantasy@0.8.0"></script>
<script>
const {equals, fromPairs, is, map, path, pipe, prop, toPairs, sequence, toString} = ramda;
const {Maybe} = ramdaFantasy;
const {Just, Nothing} = Maybe;
</script>maybeObj转换这样的结构:{
city: Just('Pyrmont'),
latitude: Just(-33.866651)
}
Just({
city: 'Pyrmont',
latitude: -33.866651
})
Nothing :{
city: Just('Pyrmont'),
latitude: Nothing()
}
Nothing :Nothing()
R.sequence 适用于数组和其他可折叠类型。 (对于 long, complicated reasons,Ramda 不会将对象视为可折叠的。)maybeSpec ,它的作用很像 R.applySpec 但将每个字段包装在 Just 中或 Nothing .Maybe from Ramda-Fantasy 。该项目已停止,我可能应该弄清楚使用那里的最新项目之一需要进行哪些更改。归咎于懒惰。唯一的我想,所需的更改是将调用 Maybe 替换为他们提供的任何函数 [或您自己的] 将 nil 值转换为 Nothing 并将其他所有值转换为 Just s。)
关于javascript - lambda : Fold an object,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56438949/
我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的
我经常将预配置的lambda插入可枚举的方法中,例如“map”、“select”等。但是“注入(inject)”的行为似乎有所不同。例如与mult4=lambda{|item|item*4}然后(5..10).map&mult4给我[20,24,28,32,36,40]但是,如果我制作一个2参数lambda用于像这样的注入(inject),multL=lambda{|product,n|product*n}我想说(5..10).inject(2)&multL因为“inject”有一个可选的单个初始值参数,但这给了我......irb(main):027:0>(5..10).inject
这段代码没有像我预期的那样执行:casewhen->{false}then"why?"else"ThisiswhatIexpect"end#=>"why?"这也不是casewhen->(x){false}then"why?"else"ThisiswhatIexpect"end#=>"why?"第一个then子句在两种情况下都被执行,这意味着我提供给when子句的lambda没有被调用。我知道无论when子句的主题是什么,都应该调用大小写相等运算符===。我想知道当没有为case提供参数时,===的另一边会发生什么。我在想它可能是nil,但它不可能是:->{false}===nil#=>
如何将lambda传递给hash.each,以便我可以重复使用一些代码?>h={a:'b'}>h.eachdo|key,value|end=>{:a=>"b"}>test=lambdado|key,value|puts"#{key}=#{value}"end>test.call('a','b')a=b>h.each&testArgumentError:wrongnumberofarguments(1for2)from(irb):1:in`blockinirb_binding'from(irb):5:in`each'from(irb):5from/Users/jstillwell/.rv
我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan
是否可以不为lambda分配上下文?例如:classRuledefget_rulereturnlambda{putsname}endendclassPersonattr_accessor:namedefinit_rule@name="ruby"Rule.new.get_rule.call()#shouldsay"ruby"butsaywhatobjectofclassRull,doesnothavevariablename#orself.instance_eval&Rule.new.get_ruleendend我的目标是->没有上下文的存储过程对象,并在特定位置调用之前分配上下文。可能
我看到有关未找到文件min.map的错误消息:GETjQuery'sjquery-1.10.2.min.mapistriggeringa404(NotFound)截图这是从哪里来的? 最佳答案 如果ChromeDevTools报告.map文件的404(可能是jquery-1.10.2.min.map、jquery.min.map或jquery-2.0.3.min.map,但任何事情都可能发生)首先要知道的是,这仅在使用DevTools时才会请求。您的用户不会遇到此404。现在您可以修复此问题或禁用sourcemap功能。修复:获取文
我有一个用Rails3编写的站点。我的帖子模型有一个名为“内容”的文本列。在帖子面板中,html表单使用tinymce将“content”列设置为textarea字段。在首页,因为使用了tinymce,post.html.erb的代码需要用这样的原始方法来实现。.好的,现在如果我关闭浏览器javascript,这个文本区域可以在没有tinymce的情况下输入,也许用户会输入任何xss,比如alert('xss');.我的前台会显示那个警告框。我尝试sanitize(@post.content)在posts_controller中,但sanitize方法将相互过滤tinymce样式。例如
要清楚-此代码运行完美-codewithproc但如果我将Proc.new更改为lambda,则会出现错误ArgumentError:wrongnumberofarguments(1for0)这可能是因为instance_eval想要将self作为参数传递,而lambda将其视为一种方法并且不接受未知参数?有两个例子-第一个是工作:classRuledefget_ruleProc.new{putsname}endendclassPersonattr_accessor:namedefinit_rule@name="ruby"instance_eval(&Rule.new.get_rule
出于某种原因,我必须为Firefox禁用javascript(手动,我们按照提到的步骤执行http://support.mozilla.org/en-US/kb/javascript-settings-for-interactive-web-pages#w_enabling-and-disabling-javascript)。使用Ruby的SeleniumWebDriver如何实现这一点? 最佳答案 是的,这是可能的。而是另一种方式。您首先需要查看链接Selenium::WebDriver::Firefox::Profile#[]=