Rail's strong_parameters not marking Array's Hashes as Permitted - ruby

I've got a bit of a puzzler on for strong_parameters.
I'm posting a large array of JSON to get processed and added as relational models to a central model. It looks something like this:
{
"buncha_data": {
"foo_data" [
{ "bar": 1, "baz": 3 },
...
]
},
...
}
And I've got a require/permit flow that looks like it should work:
class TheController < ApplicationController
def create
mymodel = MyModel.create import_params
end
def import_params
params.require(:different_property)
params.require(:buncha_data).permit(foo_data: [:bar, :baz])
params
end
end
Yet in the create method when I iterate through this data to create the related model:
self.relatables = posted_data['buncha_data']['foo_data'].map do |raw|
RelatedModel.new raw
end
I get a ActiveModel::ForbiddenAttributesError. What I've ended up having to do is iterate through the array on my own and call permit on each hash in the array, like so:
params.required(:buncha_data).each do |_, list|
list.each{ |row| row.permit [:bar, :baz] }
end
What gives?

As MikeJ pointed out - require and permit do not update the object.
I rewrote my controller to be:
def import_params
params[:different_property] = params.require(:different_property)
params[:buncha_data] = params.require(:buncha_data).permit(foo_data: [:bar, :baz])
params
end
And everything worked great. This is somewhat apparent if you read the source code.

Related

Sort a array of string by the reverse value

Right now I've produced the following code to sort a list of domains
domains = [
'api.test.google.com',
'dev.blue.google.com',
'dev.test.google.com',
'a.blue.google.com'
]
filtered = []
domains.each { |domain| filtered.push domain.reverse! }
domains.sort!
domains.each { |domain| filtered.push domain.reverse! }
The output of this code will be:
["a.blue.google.com", "dev.blue.google.com", "api.test.google.com", "dev.test.google.com"]
I'm trying to find a way to make this more elegant as it does not look like the most optimal solution to solve this problem but I'm having issues figuring out what is.
Thank you for your help!
Would this work for you?
domains.
map{|d| d.split(".")}.
sort_by(&:reverse).
map{|d| d.join(".") }
Edit: or indeed
domains.sort_by{|x| x.split(".").reverse}
Just to add, I think that something like this deserves to be a value object, as these are not simply strings and they have their own attributes and special behaviour (such as this sort).
For example:
class Domain
include Comparable
def initialize(string)
#string = string
end
def to_s
#string
end
def elements
#string.split(".")
end
protected def <=>(other)
elements.reverse <=> other.elements.reverse
end
def tld
elements.last
end
end
So you can then:
domains = [
Domain.new('api.test.google.com'),
Domain.new('dev.blue.google.com'),
Domain.new('dev.test.google.com'),
Domain.new('a.blue.google.com'),
]
domains.map(&:to_s)
=> ["api.test.google.com", "dev.blue.google.com", "dev.test.google.com", "a.blue.google.com"]
domains.sort.map(&:to_s)
=> ["a.blue.google.com", "dev.blue.google.com", "api.test.google.com", "dev.test.google.com"]
You can also add in any other behaviour you like, such as a method for returning the top level domain.
If all you want to do is sort by the reversed value use sort_by:
domains = [
'api.test.google.com',
'dev.blue.google.com',
'dev.test.google.com',
'a.blue.google.com'
]
domains.sort_by { |domain| domain.reverse }
#=> ["a.blue.google.com", "dev.blue.google.com", "api.test.google.com", "dev.test.google.com"]
If you are concerned with keeping the strings between the dots in the original order you can use:
domains.sort_by { |domain| domain.split('.').reverse }
#=> ["a.blue.google.com", "dev.blue.google.com", "api.test.google.com", "dev.test.google.com"]

Parse JSON like syntax to ruby object

Simple parser which turned out to be much harder than i thought. I need a string parser to convert nested fields to ruby object. In my case api response will only return desired fields.
Given
Parser.parse "album{name, photo{name, picture, tags}}, post{id}"
Desired output or similar
{album: [:name, photo: [:name, :picture, :tags]], post: [:id]}
Any thoughts?
Wrote my own solution
module Parser
extend self
def parse str
parse_list(str).map do |i|
extract_item_fields i
end
end
def extract_item_fields item
field_name, fields_str = item.scan(/(.+?){(.+)}/).flatten
if field_name.nil?
item
else
fields = parse_list fields_str
result = fields.map { |field| extract_item_fields(field) }
{ field_name => result }
end
end
def parse_list list
return list if list.nil?
list.concat(',').scan(/([^,{}]+({.+?})?),/).map(&:first).map(&:strip)
end
end
str = 'album{name, photo{name, picture, tags}}, post{id}'
puts Parser.parse(str).inspect
# => [{"album"=>["name", {"photo"=>["name", "picture", "tags"]}]}, {"post"=>["id"]}]

Ruby on Rails 4: Pluck results to hash

How can I turn:
Person.all.pluck(:id, :name)
to
[{id: 1, name: 'joe'}, {id: 2, name: 'martin'}]
without having to .map every value (since when I add or remove from the .pluck I have to do he same with the .map)
You can map the result:
Person.all.pluck(:id, :name).map { |id, name| {id: id, name: name}}
As mentioned by #alebian:
This is more efficient than
Person.all.as_json(only: [:id, :name])
Reasons:
pluck only returns the used columns (:id, :name) whereas the other solution returns all columns. Depending on the width of the table (number of columns) this makes quite a difference
The pluck solution does not instantiate Person objects, does not need to assign attributes to the models and so on. Instead it just returns an array with one integer and one string.
as_json again has more overhead than the simple map as it is a generic implementation to convert a model to a hash
You could simply do this
Person.select(:id,:name).as_json
You could try this as well
Person.all.as_json(only: [:id, :name])
I see three options:
1) pluck plus map:
Person.pluck(:id, :name).map { |p| { id: p[0], name: p[1] } }
2) pluck plus map plus zip and a variable to make it DRY-er:
attrs = %w(id name)
Person.pluck(*attrs).map { |p| attrs.zip(p).to_h }
3) or you might not use pluck at all although this is much less performant:
Person.all.map { |p| p.slice(:id, :name) }
If you use postgresql, you can use json_build_object function in pluck method:
https://www.postgresql.org/docs/9.5/functions-json.html
That way, you can let db create hashes.
Person.pluck("json_build_object('id', id, 'name', name)")
#=> [{id: 1, name: 'joe'}, {id: 2, name: 'martin'}]
Could go for a hash after the pluck with the ID being the key and the Name being the value:
Person.all.pluck(:id, :name).to_h
{ 1 => 'joe', 2 => 'martin' }
Not sure if this fits your needs, but presenting as an option.
You can use the aptly-named pluck_to_hash gem for this:
https://github.com/girishso/pluck_to_hash
It will extend AR with pluck_to_hash method that works like this:
Post.limit(2).pluck_to_hash(:id, :title)
#
# [{:id=>213, :title=>"foo"}, {:id=>214, :title=>"bar"}]
#
Post.limit(2).pluck_to_hash(:id)
#
# [{:id=>213}, {:id=>214}]
It claims to be several times faster than using AR select and as_json
There is pluck_all gem that do almost the same thing as pluck_to_hash do. And it claims that it's 30% faster. (see the benchmark here).
Usage:
Person.pluck_all(:id, :name)
If you have multiple attributes, you may do this for cleanliness:
Item.pluck(:id, :name, :description, :cost, :images).map do |item|
{
id: item[0],
name: item[1],
description: item[2],
cost: item[3],
images: item[4]
}
end
The easiest way is to use the pluck method combined with the zip method.
attrs_array = %w(id name)
Person.all.pluck(attrs_array).map { |ele| attrs_array.zip(ele).to_h }
You can also create a helper method if you are using this method through out your application.
def pluck_to_hash(object, *attrs)
object.pluck(*attrs).map { |ele| attrs.zip(ele).to_h }
end
Consider modifying by declaring self as the default receiver rather than passing Person.all as the object variable.
Read more about zip.
Here is a method that has worked well for me:
def pluck_to_hash(enumerable, *field_names)
enumerable.pluck(*field_names).map do |field_values|
field_names.zip(field_values).each_with_object({}) do |(key, value), result_hash|
result_hash[key] = value
end
end
end
I know it's an old thread but in case someone is looking for simpler version of this
Hash[Person.all(:id, :name)]
Tested in Rails 5.

List dynamic attributes in a Mongoid Model

I have gone over the documentation, and I can't find a specific way to go about this. I have already added some dynamic attributes to a model, and I would like to be able to iterate over all of them.
So, for a concrete example:
class Order
include Mongoid::Document
field :status, type: String, default: "pending"
end
And then I do the following:
Order.new(status: "processed", internal_id: "1111")
And later I want to come back and be able to get a list/array of all the dynamic attributes (in this case, "internal_id" is it).
I'm still digging, but I'd love to hear if anyone else has solved this already.
Just include something like this in your model:
module DynamicAttributeSupport
def self.included(base)
base.send :include, InstanceMethods
end
module InstanceMethods
def dynamic_attributes
attributes.keys - _protected_attributes[:default].to_a - fields.keys
end
def static_attributes
fields.keys - dynamic_attributes
end
end
end
and here is a spec to go with it:
require 'spec_helper'
describe "dynamic attributes" do
class DynamicAttributeModel
include Mongoid::Document
include DynamicAttributeSupport
field :defined_field, type: String
end
it "provides dynamic_attribute helper" do
d = DynamicAttributeModel.new(age: 45, defined_field: 'George')
d.dynamic_attributes.should == ['age']
end
it "has static attributes" do
d = DynamicAttributeModel.new(foo: 'bar')
d.static_attributes.should include('defined_field')
d.static_attributes.should_not include('foo')
end
it "allows creation with dynamic attributes" do
d = DynamicAttributeModel.create(age: 99, blood_type: 'A')
d = DynamicAttributeModel.find(d.id)
d.age.should == 99
d.blood_type.should == 'A'
d.dynamic_attributes.should == ['age', 'blood_type']
end
end
this will give you only the dynamic field names for a given record x:
dynamic_attribute_names = x.attributes.keys - x.fields.keys
if you use additional Mongoid features, you need to subtract the fields associated with those features:
e.g. for Mongoid::Versioning :
dynamic_attribute_names = (x.attributes.keys - x.fields.keys) - ['versions']
To get the key/value pairs for only the dynamic attributes:
make sure to clone the result of attributes(), otherwise you modify x !!
attr_hash = x.attributes.clone #### make sure to clone this, otherwise you modify x !!
dyn_attr_hash = attr_hash.delete_if{|k,v| ! dynamic_attribute_names.include?(k)}
or in one line:
x.attributes.clone.delete_if{|k,v| ! dynamic_attribute_names.include?(k)}
So, what I ended up doing is this. I'm not sure if it's the best way to go about it, but it seems to give me the results I'm looking for.
class Order
def dynamic_attributes
self.attributes.delete_if { |attribute|
self.fields.keys.member? attribute
}
end
end
Attributes appears to be a list of the actual attributes on the object, while fields appears to be a hash of the fields that were predefined. Couldn't exactly find that in the documentation, but I'm going with it for now unless someone else knows of a better way!
try .methods or .instance_variables
Not sure if I liked the clone approach, so I wrote one too. From this you could easily build a hash of the content too. This merely outputs it all the dynamic fields (flat structure)
(d.attributes.keys - d.fields.keys).each {|a| puts "#{a} = #{d[a]}"};
I wasn't able to get any of the above solutions to work (as I didn't want to have to add slabs and slabs of code to each model, and, for some reason, the attributes method does not exist on a model instance, for me. :/), so I decided to write my own helper to do this for me. Please note that this method includes both dynamic and predefined fields.
helpers/mongoid_attribute_helper.rb:
module MongoidAttributeHelper
def self.included(base)
base.extend(AttributeMethods)
end
module AttributeMethods
def get_all_attributes
map = %Q{
function() {
for(var key in this)
{
emit(key, null);
}
}
}
reduce = %Q{
function(key, value) {
return null;
}
}
hashedResults = self.map_reduce(map, reduce).out(inline: true) # Returns an array of Hashes (i.e. {"_id"=>"EmailAddress", "value"=>nil} )
# Build an array of just the "_id"s.
results = Array.new
hashedResults.each do |value|
results << value["_id"]
end
return results
end
end
end
models/user.rb:
class User
include Mongoid::Document
include MongoidAttributeHelper
...
end
Once I've added the aforementioned include (include MongoidAttributeHelper) to each model which I would like to use this method with, I can get a list of all fields using User.get_all_attributes.
Granted, this may not be the most efficient or elegant of methods, but it definitely works. :)

Inserting values into a Hash for YAML dump

I'm creating a hash that will eventually be dumped on disk in YAML, but I need to capture multiple values stored in a file on disk and insert them into a hash. I can successfully create a variable with comma separated values, but I need to insert those values into a my "classes" key:
variable_values = "class1,class2,class3"
Ultimately, I need to get them into my test hash so it simulates something like this:
test_hash = {'Classes' => ['class1', 'class2', 'class3']}
Finally, I can output them to yaml so it looks like this:
---
classes:
- class1
- class2
- class3
What's the best way to iterate through the values and insert them into the hash? Thanks for any help you can offer!
You'd probably want something like:
test_hash = {'Classes' => variable_values.split(',')}
If you're wanting to serialize Ruby Classes (I'm not able to tell for sure), you'll probably want the following code (courtesy of opensoul.org, and as used in the Small Eigen Collider)
class Module
yaml_as "tag:ruby.yaml.org,2002:module"
def Module.yaml_new( klass, tag, val )
if String === val
val.split(/::/).inject(Object) {|m, n| m.const_get(n)}
else
raise YAML::TypeError, "Invalid Module: " + val.inspect
end
end
def to_yaml( opts = {} )
YAML::quick_emit( nil, opts ) { |out|
out.scalar( "tag:ruby.yaml.org,2002:module", self.name, :plain )
}
end
end
class Class
yaml_as "tag:ruby.yaml.org,2002:class"
def Class.yaml_new( klass, tag, val )
if String === val
val.split(/::/).inject(Object) {|m, n| m.const_get(n)}
else
raise YAML::TypeError, "Invalid Class: " + val.inspect
end
end
def to_yaml( opts = {} )
YAML::quick_emit( nil, opts ) { |out|
out.scalar( "tag:ruby.yaml.org,2002:class", self.name, :plain )
}
end
end
The code currently throws an exception if you try to serialize/deserialize anonymous classes (something I could fix but don't need to), and apart from that it works well for me.

Resources