Accessing hashes with slash separated keys - ruby-on-rails

Suppose I have the following hash in one of my models named Company.
FIELDS = {
TEAM: {
'num_founders': 'Number of Founders',
'num_employees': 'Number of Employees',
'founders': {
'person_info': {
'full_name': 'Full Name',
'first_name': 'First Name',
'last_name': 'Last Name',
'location': 'Location',
'gender': 'Gender',
'biography': 'Biography',
'num_articles': 'Number of Articles'
}
}
}
}
And I have a action in my application controller which renders this hash as json:
def field_names
module_name = params[:module]
category = params[:category]
case module_name
when 'company'
render json: Company::FIELDS[category.to_sym].to_json
end
end
So now if I open localhost:3000/field_names/company/TEAM I'd get the json. But the problem I'm now facing is I need to get the sub hash of this hash too. Like I want to /field_names/company/TEAM/founders/person_info and get the respective object.
To begin with I'd split the url by slashes to get the keys. Im unable to figure out how would I use those strings to access the hash.
If I define the route like get '/field_names/:query', to: 'application#field_names
And If I hit localhost:3000/field_names/company/TEAM/founders
The action should render Company::FIELDS[:TEAM]["founders"] object which would be
'person_info': {
'full_name': 'Full Name',
'first_name': 'First Name',
'last_name': 'Last Name',
'location': 'Location',
'gender': 'Gender',
'biography': 'Biography',
'num_articles': 'Number of Articles'
}
For this my action should do something like:
def field_names
query = params[:query]
keys = query.split("/")
#keys.first::FIELDS[key2][key3]... .to_json
end
How do I achieve this? Thanks :)

How about something like this:
fields = {
TEAM: {
'num_founders': 'Number of Founders',
'num_employees': 'Number of Employees',
'founders': {
'person_info': {
'full_name': 'Full Name',
'first_name': 'First Name',
'last_name': 'Last Name',
'location': 'Location',
'gender': 'Gender',
'biography': 'Biography',
'num_articles': 'Number of Articles'
}
}
}
}.with_indifferent_access
'TEAM/founders'.split('/').each do |key|
fields = fields[key]
end
puts fields
=> {"person_info"=>{"full_name"=>"Full Name", "first_name"=>"First Name", "last_name"=>"Last Name", "location"=>"Location", "gender"=>"Gender", "biography"=>"Biography", "num_articles"=>"Number of Articles"}}
Just for console, I changed FIELDS to fields (SHOUTING_CASE is usually reserved for constants, fwiw). And, I used with_indifferent_access because your nested hashes use both strings and symbols as keys.
If you want that person_info, then:
fields = {
TEAM: {
'num_founders': 'Number of Founders',
'num_employees': 'Number of Employees',
'founders': {
'person_info': {
'full_name': 'Full Name',
'first_name': 'First Name',
'last_name': 'Last Name',
'location': 'Location',
'gender': 'Gender',
'biography': 'Biography',
'num_articles': 'Number of Articles'
}
}
}
}.with_indifferent_access
'TEAM/founders/person_info'.split('/').each do |key|
fields = fields[key]
end
puts fields
=> {"full_name"=>"Full Name", "first_name"=>"First Name", "last_name"=>"Last Name", "location"=>"Location", "gender"=>"Gender", "biography"=>"Biography", "num_articles"=>"Number of Articles"}
You probably want to put some try in there in case the url is malformed.
Also, you could abstract and simplify that bit where you go:
case module_name
when 'company'
render json: Company::FIELDS[category.to_sym].to_json
end
Assuming you have (in routes.rb):
get '/field_names/:query'
And you hit:
localhost:3000/field_names/company/TEAM/founders
Then your params should include:
{query: 'company/TEAM/founders'}
In which case you do something like:
def field_names
query_split = params[:query].split
module_name = query_split.shift.camelize
fields = "#{module_name}::FIELDS".constantize.clone.with_indifferent_access
query_split.each{|key| fields = fields[key]}
render json: fields
end
You don't have to put to_json at the end of your hash, btw.

It's a routing problem in your project. Actually, you should have a route to retrieve these fieldnames, like:
get 'field_names' => 'controller#action'
then, you can pass any parameters on your URL.
Eg.
/field_names?company=TEAM&attribute=founders
Than, those attributes would be available in your controller as
params[:company]
params[:attribute]
and so you can render only the attributes you want

Related

create a specific hash (dynamic)

How I can create a hash like this in a cycle ?
User.items.each do |m|
......
Result:
test = [{:name => 'Unit 1', :price => "10.00"},
{:name => 'Unit 2', :price => "12.00"},
{:name => 'Unit 3', :price => "14.00"}]]
You can use map to return hashes that you build.
Assuming your Item resource responds to name and price, it would look like
test = User.items.map do |m|
{
name: m.name,
price: m.price
}
end
You also can do like this:
Item.connection.select_all("select name, price from items where user_id = xxxxx;")
you will get an array containing hash, like this:
[{"name"=>"xxx", "price"=> xxx},{}......]

Passing hashes into create action in Ruby on Rails

I'm trying to create a workout routine that gets created when a workout gets created by passing this in via ajax:
Parameters: {"utf8"=>"✓", "workout"=>{"name"=>"cool workout", "desc"=>"super cool"}, "exerciseorder"=>["4", "2"], "repssets"=>{"0"=>["40", "4"], "1"=>["60", "6"]}}
Here is what my Create action looks like in my Workout Controller:
exercise_order = params[:exerciseorder]
repssets = params[:repssets]
#workout = Workout.new(workout_params)
if #workout.save
WorkoutRoutine.create(
[
exercise_order.each_with_index.map { |x,i|
{
:exercise_id => x,
:position => i,
:workout_id => #workout.id
}
},
repssets.map { |x| x.last }.each { |y|
{
:reps => y.first,
:sets => y.last
}
}
]
)
render :nothing => true
else
render json: #workout.errors.full_messages, status: :unprocessable_entity
end
If I use an opening and closing '[]' within the WorkoutRoutine.create, it tells me:
ArgumentError: When assigning attributes, you must pass a hash as an argument.
And when I change them to '{}' it tells me:
syntax error, unexpected ',', expecting =>
I've tried a myriad of different combinations and work-arounds but can't seem to figure out why it won't correctly parse the data and save it to the database, any help is very appreciated.
EDIT:
When I remove the initial {} and [] from the WorkoutRoutine.create:
WorkoutRoutine.create(
exercise_order.each_with_index.map { |x,i|
{
:exercise_id => x,
:position => i,
:workout_id => 20
}
},
repssets.map { |x| x.last }.each { |y|
{
:reps => y.first,
:sets => y.last
}
}
)
I get this error message:
ArgumentError: wrong number of arguments (2 for 0..1)
Edit2:
This is the jQuery code that sents to the data field via ajax:
var getId = $(".exercise-list").sortable('toArray');
ary = []
$(getId).each(function () {
id = $(this[0]).selector;
var reps = $("#reps" + id).val();
var sets = $("#sets" + id).val();
ary.push([reps, sets])
});
var orderRepsSets = { exerciseorder: getId, repssets: ary }
var formData = $('#new_workout').serialize();
var data = formData + '&' + $.param(orderRepsSets);
$.ajax({
url: $("#new_workout").attr('action'),
method: 'POST',
data: data,
success: (function() {
....
});
Did I get it correctly that you want to create multiple WorkloadRoutine objects, one for each exercise with the corresponding repetitions, the position, etc. If yes, then in order to do that you will have to pass an array of hashes (one hash for each object) to the WorkoutRoutine.create() function. As engineersmnky correctly stated in his comment, the data structure you are currently passing is more like [[{...},{...},{...}],[{...},{...},{...}]], but instead it should be just [{...},{...},...]. Do achieve that, something like this should do the trick:
WorkoutRoutine.create(
exercise_order.map.with_index { |x,i|
{
:exercise_id => x,
:position => i,
:workout_id => #workout.id,
:reps => repssets[i.to_s].first,
:sets => repssets[i.to_s].last
}
}
)
If you could change repssets to an array like exercise_order you could even remove the string cast for getting the reps and sets, which would simplify the whole think even more.
If it comes for errors they are quite self explanatory. But let's start from beginning..
I assume that WorkoutRoutine is an ActiveRecord::Base model. The WorkoutRoutine.create method gets 0 or 1 argument which should be a Hash or a block.
In the first iteration you were passing an Array instead of Hash, so it looked like:
WorkoutRoutine.create([a, b]) # => ArgumentError: When assigning attributes, you must pass a hash as an argument.
On the second iteration you stripped away the square brackets, what gave you 2 arguments instead of one Hash:
WorkoutRoutine.create(a, b) # => ArgumentError: wrong number of arguments (2 for 0..1)
If you read errors carefully you will start getting the idea what's happening.
About the workout routine itself
From what you specified I would assume that you want something like:
Workout has many Routines
Routine belongs to Workout and Exercise
Routine is composed of fields like
position/order,
number of repetitions,
number of sets
If my assumption is correct, then you want to use nested_attributes and then have parameters and controller like
# given parameters as workout_params
{
workout: {
name: "workout name",
desc: "workout description",
routines_attributes: [
{ position: 1, exercise_id: 4, repetitions_number: 40, sets_number: 4 },
{ position: 2, exercise_id: 2, repetitions_number: 60, sets_number: 6 }
]
}
}
# Controller
def create
#workout = Workout.new workout_params
if #workout.save
redirect_to # ...
else
render :new
end
end
private
def workout_params
params.require(:workout).permit(:name, :desc, routines_attributes: [:position, :exercise_id, :repetitions_number, :sets_number])
end
It should be strait forward how to then create a view with fields_for and update models with proper associations

Render only Items with a specific id

I render my Item's via <%= render #items%>
I added a migration AddCatIdToItems cat_id:string
Cat_id's: testA, testB, testC.
how can I render only items with the specific id testA?
e.g.: <&= #items.where(:cat_id => '1')&>
I use a seed file to populate the Item's. e.g.:
Tag.create([
{ name: 'example' },
{ name: 'example2' },
{ name: 'example3' }
])
How can i pass the cat_id into that?
is { name: 'example', cat_id:1 } working?
I am not sure what you are asking for. But I guess this is what you want
#items = Item.where("cat_id LIKE '%testA%'")
If you want to select all the items that have a specific cat_id, then
#items = Item.where(:cat_id => 'testA')

Find Or Create Rails Nested Attribute By Name

I have a Model called Example that accepts_nested_attributes_for NestedExample.
When I create a new Example Model I can also create NestedExamples:
params = { :name => 'Example 1', :nested_example_attributes => { :name => 'Nested Example 1' } }
Example.find_or_create_by_name params
This is all working fine. However, I rather than creating a new NestedExample every time, I would like Rails to perform a find_or_create_by_name on the NestedExample model, so that in the above situation, if there is already a NestedModel with a name of Nested Example 1, it will be used, rather than a new instance of NestedExample with the same name.
My current result:
params_1 = { :name => 'Example 1', :nested_example_attributes => { :name => 'Nested Example 1' } }
params_2 = { :name => 'Example 2', :nested_example_attributes => { :name => 'Nested Example 1' } }
example_1 = Example.find_or_create_by_name params_1
example_2 = Example.find_or_create_by_name params_2
puts example_1.nested_example.id == example_2.nested_example.id # Should be true

Rails Creating a Valid JSON object

I could use your helping creating a valid JSON object in Rails.
Here is an example of a valid JSON object that I'm using in the jQuery plugin: https://drew.tenderapp.com/kb/autosuggest-jquery-plugin
var data = {items: [
{value: "21", name: "Mick Jagger"},
{value: "43", name: "Johnny Storm"},
{value: "46", name: "Richard Hatch"},
{value: "54", name: "Kelly Slater"},
{value: "55", name: "Rudy Hamilton"},
{value: "79", name: "Michael Jordan"}]};
Within Rails I'm creating my object as follows:
#projects = Projects.all
#projectlist = Array.new
#projectlist << {
:items => #projects.map { |project|
{
:name => space.name,
:value => space.id
}
}
}
But this ends up outputting like so which ERRORs by the plugin:
[{"items":[{"value":74,"name":"XXXXXX"},{"value":71,"name":"XXXXXX"},{"value":70,"name":"XXXXXX"}]}]
Looks like there is a [] around the initial {} any idea why that's happening and how to build a valid JSON object?
Thanks!
Simply assign #projectlist to a Hash, like so:
EDIT After looking at the plugin's API, I've come to the conclusion that you need to convert your values to strings first:
#projects = Projects.all
#projectlist = {
:items => #projects.map { |project|
{
:name => space.name,
:value => space.id.to_s
}
}
}
Since you're initializing #projectlist to an Array and pushing the Hash onto it, you're getting those wrapping [] characters.

Resources