Dynamic hash field in Mongoid using strong parameters - mongoid

So there doesn't appear to be any clean way to generically allow Hash field with strong parameters. This may of course be a strong parameters issue but I'm curious if there is a workaround. I have a model with some fields...
field :name, type: String
field :email, type: String
field :other_stuff, type: Hash, default: {}
Now I could just permit everything:
params.require(:registration).permit!
But that isn't really a great idea and what I'd like to do is something like...
params.require(:registration).permit(:name, :email, { other_stuff: {} })
However this doesn't seem to be possible with strong parameters, it isn't possible to just whitelist a hash as a property (yay for SQL centric ActiveRecord APIs!). Any ideas how this might be done, or is my best bet to submit a Rails patch to allow for this scenario.

Ok, after researching this, I found an elegant solution that I will start using too:
params.require(:registration).permit(:name).tap do |whitelisted|
whitelisted[:other_stuff] = params[:registration][:other_stuff]
end
source: https://github.com/rails/rails/issues/9454#issuecomment-14167664

If necessary nested attributes can also be permitted as follows:
def create_params
params[:book]["chapter"].permit(:content)
end

For a field that allows nested hashes, I use the following solution:
def permit_recursive_params(params)
params.map do |key, value|
if value.is_a?(Array)
{ key => [ permit_recursive_params(value.first) ] }
elsif value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
{ key => permit_recursive_params(value) }
else
key
end
end
end
To apply it to for example the values param, you can use it like this:
def item_params
params.require(:item).permit(values: permit_recursive_params(params[:item][:values]))
end

Related

Extracting an array of values from an array of hashes in Puppet

I have the following array of hashes in hiera:
corporate_roles:
- name: 'user.1'
system_administrator: true
global_administrator: false
password: TestPassword1234
- name: 'user.2'
system_administrator: true
global_administrator: true
password: TestPassword1234
I need to extract a list of users with a give role (eg global_administrator) to be assigned later on.
I managed to use the map function to extract the data I need:
$corporate_roles = lookup('corporate_roles')
$global_admins = $corporate_roles.map | $hash | { if ($hash['global']){$hash['name']}}
notify { "global admins are: ${global_admins}":
}
However this results in undef values seemingly making their way into the array for the users that don't match the criteria:
Notice: /Stage[main]/salesraft_test/Notify[global admins are: [, user.2]]/message: defined 'message' as 'global admins are: [, user.2]'
Notice: Applied catalog in 0.04 seconds
I can get around this by using the filter function as such:
$test = $global_admins.filter | $users | {$users =~ NotUndef}
Which results in clean output:
Notice: /Stage[main]/salesraft_test/Notify[global admins are: [user.2]]/message: defined 'message' as 'global admins are: [user.2]'
Notice: Applied catalog in 0.03 seconds
But I suspect there must be a better way of doing this and I am either missing some logic in my map or I am likely using the wrong function altogether for this.
I would like to know if there is a better way to achieve what I am trying to do?
But I suspect there must be a better way of doing this and I am either
missing some logic in my map or I am likely using the wrong function
altogether for this.
map() emits exactly one output item for each input item, so if your objective is to apply a single function to obtain your wanted output from your (lengthier) input, then indeed, map will not achieve that.
I would like to know if there is a better way to achieve what I am trying to do?
Personally, I would do the job by filtering out the hashes you want from your input and then mapping those to the wanted output form (as opposed to mapping and then filtering the result):
$global_admins = $corporate_roles.filter |$hash| {
$hash['global_administrator']
}.map |$hash| { $hash['name'] }
I like that because it's nice and clear, but if you want to do it with one function call instead of two then you're probably looking for reduce:
$global_admins = $corporate_roles.reduce([]) |$admins, $hash| {
$hash['global_admin'] ? {
true => $admins << $hash['name'],
default => $admins
}
}

How does one get properties from related table as properties of it's own table in Laravel 5?

The question might sound a little bit confusing but I don't know how to explain it better in one sentence.
This describes basically what the problem is:
I have a Users table which can contain 2 types of users. I know how I can separate by role. But here's the thing, users with role 1(editor_in_chief) have different attributes than users with role 2(reviewer).
My idea was to create a table named 'reviewer_attributes' and 'editor_in_chief_attributes' and create a one-to-one relation with this table to hold the attributes for the users table.
Maybe you have a better idea, that would be great as well. But for this scenario, I would like to know if it is possible to make a call to the database and to get these users' properties from the other table as properties of the User object.
When using a DB call using relations laravel will give me something like this:
user {
id: 1,
name: "Name",
reviewer_attributes: {
attribute_1: 'attribute_1',
attribute_2: 'attribute_2',
attribute_3: 'attribute_3',
}
}
But this is what I want to object to obtain look like:
user {
id: 1,
name: "Name",
attribute_1: 'attribute_1',
attribute_2: 'attribute_2',
attribute_3: 'attribute_3',
}
I want to achieve this by a single database call instead of setting the properties after the call.
I find this a very interesting topic, I hope you could help!
If I got your problem right, you may call somthing like this:
DB::table('users')
->join('reviewer_attributes', 'users.id', '=', 'reviewer_attributes.user_id')
->find($id);
you may add select to get specific attributes of each table:
DB::table('users')
->join('reviewer_attributes', 'users.id', '=', 'reviewer_attributes.user_id')
->select('users.id', 'users.name', 'reviewer_attributes.*')
->find($id);
Update: You can also use collections to restructure your results returned by Eloquent:
$result = User::with('reviewerAttributes')->find($id);
$result = $result->get('reviewer_attributes')
->merge($result->forget('reviewer_attributes')->all())
->all();
You need export model to Json?
If so, override toArray method
public function toArray()
{
$arr = parent::toArray();
$reviewer_attributes = $this->getReviewerAttributesSomeHow();
return array_merge($arr, $reviewer_attributes);
}

How to implement a search function for array of classes

I have a User class as follows:
class User
attr_reader :username, :password
def inintialize(username, password)
#username = username
#password = password
end
end
And I want to store many of these User classes in an array as follows:
users = []
def initialize_users
user = User.new("user0", "password0")
users.push(user)
user = User.new("user1", "password1")
users.push(user)
end
And now I want to implement a searching function where I can search via username, or password. I know I can iterate over the classes comparing each field like so:
def search(username)
users.each do |user|
if user.username == username then
puts "Found"
break
end
end
end
But is there any other way to do this without the iterating or is this simply the easiest/cleanest way?
I was thinking maybe there is a way to do something like:
users["username_to_find"]
=> true
Although I am not sure how to implement that. I would believe I would have to rewrite the User class to have a built in list, but from there I am lost. I guess even if I do implement this feature there is still a iteration that has to happen.
Also I would like to access that users data from within that notation such as
users["username_to_find"].password
=> "password111"
Anyone have any ideas?
PS: User class is reduced to relevant code, it actually holds many more data members which are specific to each user such as sockets, and methods for sending data to a specific users sockets.
You really have no choice but to iterate over every element of the array and perform your matching test. Enumberable#detect is what you would want to use:
def search(username)
# return the first matching result
users.detect { |user| user.username == username }
end
This would return the first User with a matching username or nil. If you want to allow multiple results to be returned (e.g. more of what the word "search" denotes) than you would want to use Enumberable#select which returns all matching blocks:
def search(username)
# return ALL matching results
users.select { |user| user.username == username }
end
If you need to potentially match on multiple criteria (e.g. search on username and first name, etc) than you will need to take this approach. If you are only searching on username, than the solution given by #dax above is perfect.
If it doesn't need to be an Array, why not use a Hash? It provides exactly the functionality you want.
users = {}
user = User.new("user0", "password0")
users[user.username] = user
Access it like so
users['tim_the_toolman'] # => nil, there is no user by that name
users['user0'] # => returns user0
users['user0'].password # => 'password0'
use Array find and select methods. Assume you are searching by name
find returns first object which satisfies your condition
array.find { |user| user.name == searched_name } # will be either one User or nil, if neither has name eq to searched_name
select returns all objects which satisfy your condition
array.select { |user| user.name == searched_name } #will be either array of Users which have name eq searched_name or an empty array when none has this name

Mongoid 3.1 eager loading, json, and field names

Recently updated to Mongoid 3.1 from 3.0.3 and this resulted in some broken code and confusion on my side.
Say you have a pair of classes with a belongs_to/has_many relationship, like so:
class Band
include Mongoid::Document
field :name, type: String
has_many :members, :autosave => true
end
class Member
include Mongoid::Document
field :name, type: String
belongs_to :band
end
Saving all this to the database like so:
b = Band.new
b.name = "Sonny and Cher"
b.members << Member.new(name: "Sonny")
b.members << Member.new(name: "Cher")
b.save
I would in my API, be able to return a 'member' object like so:
m = Member.where(name: "Sonny").first
m.to_json
which yields the following, as expected:
{"_id":"<removed>","band_id":"5151d89f5dd99dd9ec000002","name":"Sonny"}
My client can request the full band object with a subsequent call if it wants to. However, in some cases I DO want to include the referenced item directly. With 3.0.3, I would just do the following:
m = Member.where(name: "Sonny").first
m[:band] = m.band
m.to_json
and this would add a new field with the full band information to it. With 3.1, however (it may have started in earlier versions, but I didn't test), I now get this:
{"_id":"<removed>","band_id":{"_id":"5151dc025dd99d579e000002","name":"Sonny and Cher"},"name":"Sonny"}
So, it looks like the band info has been eager-loaded into the field? Why is it stored under the key ':band_id' and not ':band'? I guess ':band' is protected, but I still don't think the data should be stored under the ':band_id' key. I suspect I am missing something here. Any ideas?
You can specify an :include option for to_json like so:
m.to_json(include: :band)
The JSON will then have a key band with the Band object converted to JSON and band_id will still be present.

How to protect a user password with bcrypt and mongoid

I've just started out using MongoDB and, in particular, Mongoid.
naturally I'd like to ensure my User's passwords are kept nice and secure, and previously I'd have done this with ActiveRecord and used bcrypt. I'm looking for a nice, clean, secure, simple way to implement the same sort of thing using Mongoid.
I've taken a look at mongoid-encryptor but I've not quite got my head around how to use it.
Assume my simplified User looks like this, as per the example in mongoid-encryptor's Readme file.
class User
include Mongoid::Document
include Mongoid::Encryptor
field :name
field :password
encrypts :password
end
And in my WebApp (using Sinatra in this case) I'd define a helper such as
def login (name, cleartxtpass)
return User.where(name: name, password: cleartxtpass).first
end
How do I get it to use bcrypt?
Is there any pre-processing I need to do with cleartxtpass or will Mongoid::Encryptor just handle that? It's not clear from the docs.
Okay well after some digging I decided to not bother using Mongoid::Encryptor but to stick to the tried and tested way I used to do these things when using ActiveRecord.
So now my User looks like
class User
include Mongoid::Document
field :name, type: String
field :password_hash, type: String
index({name: 1}, {unique: true, name: 'user_name_index'})
include BCrypt
def password
#password ||= Password.new(password_hash)
end
def password=(new_password)
#password = Password.create(new_password)
self.password_hash = #password
end
end
and my authenticate helper method looks like
def auth_user(username, password)
user = User.where(name: username).first
return user if user && user.password == password
return nil
end
That works a treat.
The simply way to do it is:
class User
include Mongoid::Document
include ActiveModel::SecurePassword
field :name, type: String
field :password_digest, type: String
has_secure_password
end

Resources