I am trying to use the following code to convert a hash of options (e.g. { :opt1 => 'a', :opt2 => 'b' } ) into parameters to be appended to a URL (e.g. example.com/page?opt1=a&opt2=b):
ActionController::Routing::Route.new.build_query_string(options)
where options is the hash. I get the following error:
wrong number of arguments (0 for 7)
The corresponding docs show that the initializer method for Route take no arguments, but I think they haven't been updated for Rails 3 yet. I know routing has seen a big update for Rails 3, so I assume this code is for Rails 2.x. Does anyone know what the Rails 3 way of doing this would be?
Thanks
Never mind - I found the answer.
Rails provides a Hash#to_query method (and for some other types too I think).
Somehow Google did a poor job of finding this for me!
Related
I am currently splitting some code out to make it more reusable, and other code more succinct.
I realize I am passing a good bit of options into the locals hash, besides just the form variable, and all of these seem to only apply directly to the input object I am creating.
I was thinking of merging the locals hash into another that contains some defaults, and passing that to the input creation function.
After some researching API docs like these:
Ruby on Rails - API - ActionView::Renderer
Ruby on Rails - API - ActionView::PartialRenderer
and digging around to find these last 2 methods in the stack trace:
From: .../.rvm/gems/ruby-2.1.5#ux-rails3.2/gems/actionpack-3.2.22.5/lib/action_view/template.rb # line 145 ActionView::Template#render_without_mini_profiler: ... 2.1.5 (#<ActionView::Template:0x007fa41e138cc8>):0 >
From: .../app/views/shared/_checkbox_group.html.erb # line 3 ActionView::CompiledTemplates#_app_views_shared__checkbox_group_html_erb___4197967411385159258_70171427980900: ... 2.1.5 (#<#<Class:0x007fa40f30fec0>:0x007fa40fe71c50>):0 >
RVM, Ruby, Rails, Formtastic, Pry, Byebug, & Pry-Byebug.
Old, not-so-helpful workaround answer
As it may be convenient from the question's phrasing, one method is to not spend time diving into the Rails template rendering framework code and documentaion, and instead simply provide the options I mentioned for the locals hash, inside of a single property containing a new hash containing them, and pass that into the template.
Later I use that hash to merge with the default hash I had inside of the template and pass that to the input function as normal.
I could leave the Q&A here in case there might still me reasons people would want to directly access the locals hash, or could even develop an alternative approach that would be possible as well.
Update
So I dove into the Rail template rendering framework...
Exposition
I started simply taking this:
2.1.5 (#<#<Class:0x007fc01d352e08>:0x007fc018879698>):0 > self.class
=> #<Class:0x007fc01d352e08>
That wasn't much helpful, then went up into actionpack-3.2.22.5/lib/action_view/template.rb:145 to find this:
2.1.5 (#<ActionView::Template:0x007fc011e90510>):0 > method_name
=> "_app_views_shared__adrad_index_html_erb___3381877171036202789_70231455466120"
2.1.5 (#<ActionView::Template:0x007fc011e90510>):0 > view.method(method_name.to_sym)
=> #<Method: #<Class:0x007fc01d352e08>(ActionView::CompiledTemplates)#_app_views_shared__adrad_index_html_erb___3381877171036202789_70231455466120>
2.1.5 (#<ActionView::Template:0x007fc011e90510>):0 > view.method(method_name.to_sym).source_location
=> ["<our project_path>/app/views/shared/some.html.erb", 0]
Where the last point wasn't great, but then I started looking for ActionView::CompiledTemplates, which unfortunately, you cannot use source_location to find, or instance variable declarations for that matter (different issue :) ).
I decided to randomly search the actionpack-.../lib/action_view/ directory to find a reference or so in context.rb, but instead of looking inside of template.rb, I skipped straight to searching for locals in that gem's files now.
resolver.rb did not help much, and neither did template_renderer.rb.
Once I delved into template.rb and found the familiar above stack frame for render that my answer had to be between that and my actual page with the included locals properties,
I followed compile! to its definition,
saw a familiar ActionView::CompiledTemplates,
then a compile,
found an interesting arbitrary, literal, meta source manipulation, as I was sort of expecting with the lack of more stack frames in-between to scour, and the callback-esque naming scheme, I found this curious variable, as a parameter to the dynamically-generated template function: local_assigns.
I quickly jump around to locals_code's definition since it is also used and verify this is source variable enumeration I was looking for!
Answer
local_assigns!
Also, I currently don't believe the hash provides any properties I would want to avoid, such as any information specific to only the current partial template page that I wouldn't want to be propagated to the child partial template page.
Update
I am starting to do more with this variable, and I wonder if I am limited.
I want to mix-in some default values, which might involve using binding.local_variable_set, but it is not able to create new local variables (How to dynamically create a local variable?), so I might just have to use my own nested data structure now..
I have an EmberJS filter like this:
/app/routes/trails/new.js
model: function (filterCurrentEmployees) {
return Ember.RSVP.hash({
trail: this.store.createRecord('trail'),
employees: this.store.query('employee', { status: '1,2'}).then(
function(data) {return data})
})
},
I was hoping that status: '1,2' would end up as a normal Rails param so I could do params[:status] and then filter the returned employees (you know so the db would get a query like 'where status IN ['1','2']')
But when this filter query is sent to Rails API I get this error:
ActiveModelSerializers::Adapter::JsonApi::Deserialization::InvalidDocument (Invalid payload ({:data=>"Expected hash"}): {"status"=>"1,2", "controller"=>"employees", "action"=>"index"}):
which occurs here in the controller:
api/app/controllers/employees.rb
def employee_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(params)
end
but that is needed for AMS to work with Ember.
So I presume something is wrong with the way I am sending/creating the query in EmberJS?
It seems to be trying to make a GET collection request. Not sure really and this explains sort-of what is going on at the Rails end, but I don't know how to get Ember to create the filter properly so that AMS is happy to accept it.
EDIT - JSON-API Spec Reference
I was under the impression that AMS and Ember 2.7 with JSON-API 'just work out of the box'. I was actually expecting the Ember filter to comply with the spec and send
/employees?filter=status[1,2]
but it seems to not be doing that. AMS says the same thing here.
UPDATE
So with some pointers from the comments (thank you) I learned that the structure of the query hash might be incorrect. I also tried changing 'query' to filter but that then raises an actual Ember error:
The filter API has been moved to a plugin. To enable store.filter using an environment flag, or to use an alternative, you can visit the ember-data-filter addon page.
Now I don't know what the difference is between an Ember query and an Ember filter, perhaps a filter only runs on the client? Who knows, the docs are so sparse and so many questions on this are 1 or 2 years old.
So after much digging around for Ember Gold Nuggets, I found out that Controllers are STILL needed for...you guessed it...Query Parameters.
I've never used a Controller, and thought I never needed one.
So it seems I was basing my attempt at using query parameters on outdated information and code examples.
For those who run into this, the gold nuggets are here and here.
Also, it seems Javascript Object parameters as per JSON-API spec are not yet supported in Ember, according to this, although it's nearly a year old so do not know if that is still true.
UPDATE
So, after further gold mining, it seems I was confused and that Controllers are needed for Query Parameters in Ember on the client side, to assist the frontend application when it transitions from one route to another (and that is when you need a Controller to set them up).
But I want to just send a query parameter from within the Route code directly to the API, and examples of doing that are very hard to find.
So in the Rails Log I would expect to see BEFORE deserialization by ASM:
Rails.logger.info "Params are: #{params.to_json}"
something like this:
Params are:
{"data":{"filter":{"status["1,2"]}},
"controller":"employees","action":"index"}
ASM expects 'data' to be the root element of the hash, and then inside that I can place my filter. For example, from the Ember Route:
model: function () {
let myFilter = {};
myFilter.data = { filter: {status: ['1,2']}};
return Ember.RSVP.hash({
trail: this.store.createRecord('trail'),
employees: this.store.query('employee', myFilter).then(function(data) {return data})
})
},
I am using a 4-year old Rails tutorial and I have Rails 4.0.2. I made a model called "Thing" and a controller called "Things". The "Thing" model has one attribute called "data". In my create action, I had this line:
#thing = Thing.new(params[:thing])
which results in this error:
ActiveModel::ForbiddenAttributesError in ThingsController#create
I found a StackOverflow thread that said I needed to require my needed parameters, and that worked just fine.
Before I looked that up I tried putting the hash from my params directly into the Thing.new() method and I didn't get an error. I started with this line:
puts params[:thing]
in my create action, typed "12345" in my text field, hit submit and got this in the console:
{"data"=>"12345"}
So I tried this in the create action:
#thing = Thing.new({"data" => "12345"})
and I didn't get the error. I even confirmed they were identical by doing this:
puts params[:thing] == {"data"=>"12345"}
and I get "true" on the console. So,
Thing.new(params[:thing])
gives me the error, but
Thing.new({"data"=>"12345"})
does not.
How can Rails tell the difference between these two arguments when they seem to be identical?
params[:thing] is not the same thing as {"data" => "12345"}, they just have the same value when inspect is called on them, and params's class overrides == to say it's equal to the hash.
Rails 4+ uses Strong Parameters, which is a security feature to make sure you know what you're putting in your models. Basically, Rails wants to you check the validity of the parameters. It lets you do Thing.new({"data" => "12345"}) because you, the developer, are creating the Hash directly, and are more trustworthy than someone on the internet calling your server.
Total Rails noob, working through the Rails Tutorial videos. I'm all the way to the last lesson, and there's something I don't understand:
#users = #user.followed_users.paginate(page: params[:page])
Specifically, the bit I'm not tracking on is paginate(page: params[:page]). I looked at the paginate docs and I understand the paginate method can take three params, :page being one of them. I think this parameter means "current page," but the will_paginate docs say it defaults to 1.
I also know (think) that params[:page] refers to the built-in Rails params hash, meaning, current session params. Right?
So... I don't get it. Why do I need it? How does the :page symbol get into the params hash? What does this really do?
For additional context, see listing 11.30 on the Ruby Tutorial book. Any help would be much appreciated.
I think what you might be misunderstanding is how Ruby arguments work in this case. paginate does not actually take 3 arguments, but instead takes a single hash argument with three options (key/value pairs).
In Ruby, when you pass key/value pairs as the last set of arguments, they are automatically converted to a hash. For example, the following are equivalent:
paginate({page: 1})
is the same as:
paginate(page: 1)
So really what you are doing is passing a single argument, which is a hash that has multiple key/value pairs.
Now to specifically answer your questions:
Why do I need it?
You need to pass this value in so that will_paginate knows which page you are currently on. It defaults to page one because on the initial page load, you will not have ?page=x in your URL. After you change to a different page, it takes the page value from the URL and passes that to the paginate method.
How does the :page symbol get into the params hash?
Any argument that is part of the query params in the URL will get automatically passed to the params hash by Rails (more likely Rack which Rails is built upon)
What does this really do?
I'm hoping the above answered this, but if not, maybe it provided you with enough info to come up with a more specific question.
I have a string like this:
"foo=bar&bar=foo&hello=hi"
Does Ruby on Rails provide methods to parse this as if it is a querystring, so I get a hash like this:
{
:foo => "bar",
:bar => "foo",
:hello => "hi"
}
Or must I write it myself?
EDIT
Please note that the string above is not a real querystring from a URL, but rather a string stored in a cookie from Facebook Connect.
The answer depends on the version of Rails that you are using. If you are using 2.3 or later, use Rack's builtin parser for params
Rack::Utils.parse_nested_query("a=2") #=> {"a" => "2"}
If you are on older Rails, you can indeed use CGI::parse. Note that handling of hashes and arrays differs in subtle ways between modules so you need to verify whether the data you are getting is correct for the method you choose.
You can also include Rack::Utils into your class for shorthand access.
The
CGI::parse("foo=bar&bar=foo&hello=hi")
Gives you
{"foo"=>["bar"], "hello"=>["hi"], "bar"=>["foo"]}
Edit:
As specified by Ryan Long this version accounts for multiple values of the same key, which is useful if you want to parse arrays too.
Edit 2:
As Ben points out, this may not handle arrays well when they are formatted with ruby on rails style array notation.
The rails style array notation is: foo[]=bar&foo[]=nop. That style is indeed handled correctly with Julik's response.
This version will only parse arrays correctly, if you have the params like foo=bar&foo=nop.
Edit : as said in the comments, symolizing keys can bring your server down if someone want to hurt you. I still do it a lot when I work on low profile apps because it makes things easier to work with but I wouldn't do it anymore for high stake apps
Do not forget to symbolize the keys for obtaining the result you want
Rack::Utils.parse_nested_query("a=2&b=tralalala").deep_symbolize_keys
this operation is destructive for duplicates.
If you talking about the Urls that is being used to get data about the parameters them
> request.url
=> "http://localhost:3000/restaurants/lokesh-dhaba?data=some&more=thisIsMore"
Then to get the query parameters. use
> request.query_parameters
=> {"data"=>"some", "more"=>"thisIsMore"}
If you want a hash you can use
Hash[CGI::parse(x).map{|k,v| [k, v.first]}]