How does rails parse POST request parameters? - ruby-on-rails

I am working on an application where I send a post request to my rails server with parameters stored in JSON format. Let's say my application routes the request to the create function on my cats_controller. The cat model has three fields :name, :hunger, :mood. Now each cat has_many kittens. My Kitten model has two fields :cat_id (referring to which cat owns it, because my kitten belongs_to a cat) and :cute. Now every time i create a cat on my application I want to do it with a single call, so I call /cats.json from my app with a POST request. The parameters are stored in JSON format and they include the following fields {name:"",hunger:"", mood:"",cute:""}. Now the controller takes these params creates a new cat, and then creates a new kitten assigned to this cat using cat.kittens.build(). The kitten then just needs to use the last parameter I sent "mood:" to get created properly.
Now the question is this, when I print the params variable from this controller I get the following hash: {name:"", hunger:"", mood:"", cute:"", cat:{name:"", hunger:"", mood:""}}. Why does this happen?
How does Rails parse the POST request params and take me from
{name:"",hunger:"", mood:"",cute:""}
to
{name:"", hunger:"", mood:"", cute:"", cat:{name:"", hunger:"", mood:""}}
How is this "cat" hash generated, when, and what rules does it follow?
Then my followup question would be, since rails 4 forces you to whitelist parameters before you use them. I am doing:
params.require(:cat).permit(:name,:hunger,:mood)
How do I also permit the :cute value?

You'll be best reading up on HTTP - Difference between GET and POST.
It's not Rails which sends the request, it's plain old HTML.
The difference you're looking at is how your server picks up the POST params, considering GET params are passed through the url...
Note that query strings (name/value pairs) is sent in the URL of a GET
request:
Note that query strings (name/value pairs) is sent in the HTTP message
body of a POST request
Thus, the base level answer to your question is that you need to append your POST params to the message body of the request. The best example I know of how to do this is with JQuery ajax:
$.ajax({
url: ...,
data: {your: key, value: pairs}
});
To answer your other questions, here's the structure you should use:
#app/models/cat.rb
class Cat < ActiveRecord::Base
#columns id | name | mood | created_at | updated_at
has_and_belongs_to_many :kittens
class_name: "Cat",
join_table: :kittens,
foreign_key: :cat_id,
association_foreign_key: :kitten_id
alias_attribute :born, :created_at #-> allows you to call #cat.born
end
#kittens table
#columns cat_id | kitten_id
You can read up about the self join here: Rails: self join scheme with has_and_belongs_to_many?
This should give you the ability to create a cat, and have him/her assigned as a kitten of another cat:
#app/controllers/cats_controller.rb
class CatsController < ApplicationController
def new
#cat = Cat.new
end
def create
#cat = Cat.new cat_params
#cat.save
end
private
def cat_params
params.require(:cat).permit(:name, :mood, :kittens)
end
end
This will give you the ability to have the following:
#app/views/cats/new.html.erb
<%= form_for #cat do |f| %>
<%= f.text_field :name %>
<%= f.text_field :mood %>
<%= f.collection_select :kittens, Cat.all, :id, :name %>
<%= f.submit %>
<% end %>
This will also allow you to call:
#cat = Cat.find params[:id]
#cat.kittens.each do |kitten|
kitten.mood #-> "cute"

This is down to something called parameter wrapping
It's there as a convenience so that you don't have to submit the root (ie in your case put everything in the user element) but still get to use params[:user]
By default if you have parameter wrapping on for the request format then CatsController will do this for any parameter that matches Cat.attribute_names. You can customise how parameter wrapping is done for a controller (or turn it off, on control which content types trigger it) with the wrap_parameters method, for example
class CatsController < ActionController::Base
wrap_parameters :cat, include: [:cute]
end
To also include the cute in the list of parameters to wrap and you can then do
params.require(:cat).permit(:name, :hunger, :mood, :cute)

#Frederick Cheung: thank you so much for the link. Like #Curse, I have been trying to figure out why I get two copies (ie: duplicates) of my JSON HTTP POST params printed in the rails log. Adding
wrap_parameters format: []
at the top of my controller definition gave me
{name:"",hunger:"", mood:"",cute:""}
in my log output instead of
{name:"", hunger:"", mood:"", cute:"", cat:{name:"", hunger:"", mood:""}}
because I realized I had
wrap_parameters format: [:json]
in config/initializers/wrap_parameters.rb. Having wrap_parameters enabled can almost double log size if large JSON documents are being POSTed to a Rails 3 server...

Related

Rails - How get first record of associated table?

i am trying to create an api for my mobile app.
I have posts and images tables. For my api, i can send all posts with:
#posts = Post.all
render json: #posts
Output: [{"id":20,"title":"Title 1", "body":" first post ", "user_id":1 }]
But it does not contain images at all. In order to show a showcase image in homepage of my app, i just need the first image of associated images.
The output which i need is (the name of showcase_image attribute does not matter) :
Output: [{"id":20, "title":"Title 1", "body":" first post ", "showcase_image": 'first_image.jpg' , "user_id":1 }]
I need to include first image from associated images table to my json response..
Thanks in advance !
I would suggest using a serializer. Active Model Serializer is pretty standard and easy to use, but is not receiving any updates and has a bad performance. You can choose any other serializer (I recommend Blueprinter) or use the AMS.
Through the AMS you coudl define the relation you want to serialize and it would build the json you're expecting
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :showcase_image, :user_id
def showcase_image
object.images.first.name # don't know what is the attribute you're looking for
end
end
And on your controller:
#posts = Post.includes(:images).all # Use includes to avoid N+1 problems
render json: #posts, serialize_collection: PostSerializer
You can include associations with the :include option when calling as_json.
render json: #posts.as_json(include: :images)
You could limit this to one image by adding a new association to Post.
class Post < ApplicationRecord
has_many :images
has_one :showcase_image, class_name: 'Image'
end
This would allow you to use the :showcase_image instead.
render json: #posts.as_json(include: :showcase_image)
You could also use Jbuilder to solve the issue at hand without adding an additional association.
# app/views/posts/index.json.jbuilder
# Get images that belong to posts, group them by post_id and
# return the minimum image id for each post_id.
images = Images.where(post_id: #posts.select(:id)).group(:post_id).minimum(:id)
# Request the full image data for all image ids returned above.
images = images.keys.zip(Image.find(images.values)).to_h
json.array! #posts do |post|
json.extract! post, :id, :title, :body, :...
json.showcase_image do
image = images[post.id]
if image
json.extract! image, :id, :name, :location, :...
else
json.null!
end
end
end
Without calling a specific render, Rails will default to the app/views/posts/index file, and select the file matching the request. (If you request HTML it will look for an HTML file, if you request JSON it looks for JSON, etc.)
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
end
Now when you request /posts.json or /posts with the header Accept: application/json your application should return the JSON response build by Jbuilder.

Setting Nested Attributes of an existing record based on specific parameters

My first post here so I hope I'm including all the necessary information! Happy I've managed to make it two months into learning without having to ask any questions yet. :-)
I have a Callsheets record with a nested Store record that contains a :lastvisit column that I would like to update each time a new Callsheet is submitted every month. The :lastvisit field should be updated where the Store :id == #callsheet.store_id which is already defined. The beginner in me thinks the correct code for the 'update' method would be
#callsheet.update(callsheet_params).where(#callsheet.store_id => #store.id)
but I'm not sure how to access #store in this instance, and this likely just creates a new record anyways.
Any help or points in the right direction are appreciated. Thanks!
I've been trying to get it running in the 'update' method, but would also like to get it running in the 'create' method if that's any different. Relevant info:
callsheet.rb:
class Callsheet < ActiveRecord::Base
belongs_to :store
accepts_nested_attributes_for :store
callsheets_controller.rb:
class CallsheetsController < ApplicationController
def update
#callsheet = Callsheet.find(params[:id])
if #callsheet.update(callsheet_params)
redirect_to callsheet_dashboard_path
else
render action: :edit
end
end
def callsheet_params
params.require(:callsheet).permit(:id, :user_id, :store_id, . . . , store_attributes: [:id, :lastvisit])
edit.html.erb:
<%= form_for #callsheet, url: callsheet_path, remote: true, :html => { :multipart => true} do |f| %>
<%= f.fields_for :store do |s| %>
<%= s.text_field :lastvisit, :id => 'lastvisitHidden' %>
<% end %>
<%= f.hidden_field :store_id, :id => 'storeSelectHidden' %>
//
If you have a store_id column on callsheet that's called a "foreign key", and you can write these associations on the models:
#callsheet
belongs_to :store
#store
has_many :callsheets
Then in your controller action, after #callsheet.update(callsheet_params):
#callsheet.store.update(last_visit: Time.now)
This is simplest, and is what I'd recommend. #callsheet.store returns a store instance (you always need to keep track of whether your variables are model instances, model queries, or arrays.)
You could alternatively say this, it's just showing another way to access the store:
Store.find_by(id: #callsheet.store_id).update(last_visit: Time.now)
find_by is known as a "query method" (see ActiveRecord query interface), but unlike most of the others it returns an instance and not a query. update is only callable on instances, and returns a boolean indicating whether the save was succesful. update also triggers the callback chain - see ActiveRecord Callbacks.
Another way to do it:
Store.where(id: #callsheet.store_id).update_all(last_visit: Time.now)
This uses the .where query method, which returns a query (in this case you know it will be one element), and then the update all query method (which does not trigger the callback chain).
The last is kind of confusing and I'd dissuade you from using it. The reason being that a callsheet has only a single store, so loading it as an array is misleading.

Enter name in form but save the id as foreign key

I am trying to create a form where one can enter the :name of a foreign model-instance which should be transformed to the :id of that object to be stored as a foreign key.
I have found different ways of doing this, but they all have big disadvantages.
For example: I have two models A and B:
class A < ActiveRecord::Base
belongs_to :b
validates :b_id, presence: true, inclusion: {in: B.all.map(&:id)}
end
class B < ActiveRecord::Base
end
The _form.html.erb uses this field where one can enter the string:
<%= form_for(#a) do |f| %>
...
<div class="field">
<%= f.label :b %><br>
<%= f.text_field :b %>
</div>
...
<% end %>
While the controller for A looks like this:
class AController < ApplicationController
...
before_action :get_a_id, only: [:create, :update]
...
private
def page_params
params.require(:a).permit(:name, :b_id, :content)
end
def get_a_id
b_name = params[:a][:b]
b = B.find_by(name: b_name)
b_id = b.id unless b.nil?
params[:a][:b_id] = b_id
end
end
This works somehow if the entered :name at the text-field has been found in the database. If it is not found, the validation-errors are shown, but the text_field has not been highlighted and it is empty then (instead of containing the previously entered string).
I think this is because the field should contain the Object of B instead of the name and this does not exist in case of a wrong string.
It isn't a good idea at all to manipulate it like this, I think.
Is there a nice way to achieve that? I don't want a drop-down here because there might be a lot of values in there later. Of course the next step will be to implement some sort of auto-completion, but that should not count here.
In theory there should be a way to associate the text_field with a new B-object but just show the :name. The controller could then try to find a database object with that name and replace the placeholder with it. If it does not find the right object, the validation fails, but the value is still shown (of course the validation needs to be changed then).
Any way to achieve such a thing in a nice way is appreciated. Just let me know if anything is unclear.
EDIT:
What I actually want to achieve:
A form-field that allows to enter a string. That string should be passed to the controller where a search is performed that transforms this string into the id of the object (B), so that it can be used as foreign key. When the Object of class A is saved, a validator should check if the ID is set and exists. If that validation fails, the field (with the string) should be highlighted and the error-message should be shown.
The main-problem is, that the string with the name is not in the model. Just the id is. So if the id is validated (what would be the right approach), the error-messages would not be associated to the field containing the name as string.
Update: after getting the full picture:
You want virtual attributes. Then you can do, #a.set_b_name= and it will do the lookup and apply the name, or you can store the name in an instance var and use validations on it.
See these links for more detailed info :)
http://railscasts.com/episodes/167-more-on-virtual-attributes?view=asciicast
Rails: Looking up foreign key id using virtual attribute failing
Old answer was:
if B cannot be found, i.e. b.nil?, then you make a new 'b' object that isn't in the database; b = B.new(name: params[:name]).

rails formatting a custom json output

How do would I format my model so that it will output a json document with an id and name field?
Because my model has custom field names and I am using tokeninput and it requires me to output it to id and name.
any ideas?
You have so many options here, you can use jbuilder, rabl. But I think the easiest one is to use Active Model Serializers.
Let's say you have a model name User.
First install the bundle, then:
rails g serializer user
Then at app/serializers/user_serializer.rb:
class ArticleSerializer < ActiveModel::Serializer
attributes :id, :name
end
You might want to pass a only option to to_json
:only => [:id, :name]
For example, if you want to get id and name of User
User.all.to_json :only => [:id, :name]
If the model does not contain Id and Name as described by OP. Then you use custom select at the time of querying the db.
User.select('filed1 as id, field2 as name').all.to_json
Maybe in your controller
require 'json'
# ...
def show_json
user_obj_to_json = User.first.to_json
render user_obj_to_json
end

How to customize simple_form for json nested hash

I have some nested data:
#preset = Preset.new
#fields is a postgres json data type
#preset.fields = {'primary_category' => {'category_id' => 57882}}
I would like to have the same nested structure preserved in the POST params[:preset][:fields] from a form submit so I have this in my form partial:
<%= text_field_tag("preset[fields][primary_category][category_id]",nil) -%>
Simple form does not know how to deal with postgres new types like hstore or json types. In my case I don't really need it for validation or to detect the data type. Is there a way I can extend SimpleForm to skip detection of the column types and just output the same existing bootstrap boilerplate that it outputs for textfields, but for my arbitrary json nested keys?
Maybe a usage like this:
<%= f.input 'preset[fields][primary_category][category_id]', :as => :json_text_field %>
To output the same thing as helper above, but surrounded with the label, and control-group classed divs, etc.
I have looked into extending the input base class per the documentation.
class JsonTextFieldInput < SimpleForm::Inputs::Base
def input
"#{#builder.text_field(???, input_html_options)}".html_safe
end
end
But here is where I get lost as I'm not sure what to pass to the #builder to bypass checking of attribute name with my own logic to map it hash keys. Also that only changes the form input and not the label, which needs some modification as well. In either case I wasn't able to get very far and I could use some guidance.
I'm using this for jsonb / json input:
class JsonbInput < SimpleForm::Inputs::StringInput
def input()
out = ActiveSupport::SafeBuffer.new
Hash[object.send(attribute_name).sort].each do | k, v|
out << template.content_tag(:div, class: 'group') do
template.concat #builder.label(k, object.send(attribute_name), label_html_options)
template.concat #builder.text_field(k, input_html_options)
end
end
out
end
def input_html_options
{class: 'string form-control'}
end
end
You also need to use store_accessor in your model.

Resources