I've got a single-table-inheritance setup where I have a single Controller (I felt having multiple would be duplicative). However, for some methods, I'd like to call into the subclasses of the models. I figured I could have the browser send a parameter that I'd write a case statement against. Something like:
case #model[:type]
when "A"
#results = Subclass1.search(params[:term])
when "B"
#results = Subclass2.search(params[:term])
...
end
Alternatively though, I learned that Ruby, in all it's trickery can create a model out of a string. Something like:
#results = params[:model].constantize.search(params[:term])
My question: is this a bad practice? I can imagine someone sneaky could craft a request that would get me to form an arbitrary internal object.. but I could confirm that the object is a subclass of the thing I want..
When doing this, i like to refactor it with case, just to be very clear about my allowed inputs:
#results = case params[:model]
when 'page' then Page
when 'post' then Post
else raise 'finger'
end.search(params[:term])
If you have a whitelist of objects that you check it against before you do it, then you should be ok. You just always want to make sure you are santizing and validating input coming from external sources very throughly to protect yourself.
This snippet uses Ick's maybe for simplicity, but write it as you feel comfortable, the point is simply to use a hash:
#results = {"A" => Subclass1, "B" => Subclass2}[params[:model]].maybe.search(params[:term])
Related
I'm calling controllers from middleware like:
#status, #headers, #articles = ArticlesController.action('index').call(env)
And really, I just want the html from that #articles but the #articles is a huge big Rack::BodyProxy object and the html is buried deep in it.
The stranger thing is that I find it like:
articles_html = #articles.instance_variable_get(:#body).instance_variable_get(:#stream).instance_variable_get(:#buf)[0]
but another developer accesses it like this:
articles_html = #articles.instance_variable_get(:#response).instance_variable_get(:#stream).instance_variable_get(:#buf)[0]
So that makes for a pretty ugly ternary:
articles_html = #articles.instance_variable_get(:#response).nil? ? #articles.instance_variable_get(:#body).instance_variable_get(:#stream).instance_variable_get(:#buf)[0] : #articles.instance_variable_get(:#response).instance_variable_get(:#stream).instance_variable_get(:#buf)[0]
Plus, I'm not sure if that's going to cover all the cases.
What's a better way to approach this?
I know this is a very old question but in the off chance that anybody stumnles upon this...
Any method call to an instance of Rack::BodyProxy ultimately gets passed on to the resulting body array. That means you can simply call first to retrieve the body string, or call each to iterate through the array.
You can see the delegation here
Just thanks for the previous answer, you can also call .join if you want just have the full body string in a variable.
I have a client that is sending params such as age, gender, name and so on.
I need to retrieve data from the table based on the params, but first I need to check for the presence of the param(to avoid a null param and therefore an empty result). The params are working as filters, so they can be triggered or they can be left blanck.
What I am doing right now is
#retieve = Student.all
unless params[:age].nil?
#retrieve = #retrieve.where(age: params[:age])
end
unless params[:gender].nil?
#retrieve = #retrieve.where(gender: params[:gender])
end
and so on for every param I receive. This way I check if the filter has been selected, and if it has I use the selection as a parameter for the query
It works, but as Ruby is known for the DRY statement, I am pretty sure someone out there knows a better way for putting this and to make this flexible.
Thank you for whatever answer or suggestion you will provide!
This will work best if all of these filters were in a subhash of params that you can iterate over without including unwanted parameters (eg the :action and :controller parameters that rails adds)
Once you've done that you could do
(params[:filters] || {}).inject(Student.all) do |scope, (key, value)|
scope.where(key => value)
end
There's a few ways to do this sort of thing and you have options for how far you want to go at this stage.
Two big things I'd consider -
1) Make nice scopes that allow you to send a param and ignore it if it's nil. That way you can just append another scope for each param from the form and it will be ignored without using if or unless
2) Move the search into a separate class (a concern) to keep your controller clean.
Here's a blog post that talks about some of the concepts (too much to post in this answer). There is lots of info on the web about this, I searched on the web under "rails search filter params concern" to get an example for you.
http://www.justinweiss.com/blog/2014/02/17/search-and-filter-rails-models-without-bloating-your-controller/
I am currently in the process of making my first iphone app with a friend of mine. He is coding the front end while I am doing the back end in Rails. The thing is now that he is trying to send necessary attributes to me with a post request but without the use of a nested hash, which means that that all attributes will be directly put in params and not in a "subhash". So more specifically what I want to do is be able to retrieve all these attributes with perhaps some params method. I know that params by default contains other info which for me is not relevant such as params[:controller] etc.. I have named all attributes the same as the model attributes so I think it should be possible to pass them along easily, at least this was possible in php so I kind of hope that Rails has an easy way to do it as well.
So for example instead of using User.new(params[:user]) in the controller I have the user attributes not in the nested hash params[:user] but in params directly, so how can I get all of them at once? and put them inside User.new()?
I found the solution to my problem. I had missed to add the attr_accessible to my model which was what initially returned the error when I tried to run code like: User.new(params) having been passed multiple attributes with the post request.
The solution was very simple, maybe too simple, but since this is my first real application in Rails I feel that it might be helpful for other newbies with similar problems.
If you would like to just pass a more limited version of params to new, you should be able to do something like the following:
params = { item1: 'value1', item2: 'value2', item3: 'value3' }
params.delete(:item2)
params # will now be {:item1=>"value1", :item3=>"value3"}
Also see this for an explanation of using except that you mention in your comment. An example of except is something like:
params.except(:ssn, :controller, :action, :middle_name)
You can fetch the available attributes from a newly created object with attribute_names method. So in this special example:
u = User.create
u.attributes = params.reject { |key,value| !u.attribute_names.include?(key)
u.save
I'm trying to send a POST request from an external Ruby script to a Rails app via HTTP#post_form. The request is made to the create action (i.e. the URI is http://server/controller).
If I encode a single parameter into the request, everything is fine:
HTTP::post_form(uri, { :my_param => "value" })
Though I do have to explicitly pull out my_param from params manually, in the controller. This seems inefficient, and breaks creating a new record from within the app itself (because that parameter is not there). I'm consequently trying to make my script pose as Rails itself, passing the appropriate data as the controller would expect it, e.g.
HTTP::post_form(uri, { :object => { :my_param => "value" } })
However, this doesn't work. post_form seems to be escaping my hash into something different, i.e.
{ "object" => [\"my_param\", \"value\"] }
Which obviously doesn't do the same thing. Am I missing something obvious in the way I'm passing the data? Or can I not achieve what I'm after (creating a new record from outside the app)?
One straightforward way might be to simply imitate how Rails formats its parameters, like this:
params = { :my_param => "value", ... }
params = Hash[params.map { |key,value| ["object[#{key}]",value] } ]
HTTP::post_form(uri, params)
Edit: Well, look at that, I looked around a bit and found that Rails actually gives you a method to do the same thing using their own mechanism:
require 'active_support/core_ext'
...
HTTP::post(uri, parameters.to_param)
The to_param method will treat Arrays correctly, and everything else too. Notice that in this case you want to use HTTP::post, not post_form, since the parameters are already converted to a string.
I don't know much about post_form but the natural solution for me would be to use an ActiveResource object.
ActiveResource is available to ruby as well as to Rails. you use it just like you use a model only it posts and gets using XML
There is a Railscast on how this works here
http://railscasts.com/episodes/94-activeresource-basics
http://railscasts.com/episodes/95-more-on-activeresource
I think you'll find that this is a better fit for your requirements than post_form but as I say, I'm not familiar with post_form.
In my posts model, I have a named scope:
named_scope :random, :order => "Random()"
I'd like to give users the ability to get posts in a random order by sending a GET request with params[:scope] = 'random'.
Short of eval("Post.#{params[:scope]}"), how can I do this?
I would suggest my very awesome acts_as_filter plugin designed for user-driven filtering of results via named_scopes.
http://github.com/tobyhede/acts_as_filter/tree/master
Eval is fine to use - but make sure you validate against accepted/expected values (I often just plug some values into an array and test accepted_values.include?(parameter))
eval is a pretty bad idea. However, #send is perfect for this - it's inherently safer, and faster than eval (as I understand it).
Product.send(params[:scope])
That should do it :)
I came across it in a search. searchlogic is perfect for this.
I would stay away from eval since you're dealing with data that comes from the user. Maybe just use a simple case statement? This way you'll be able to validate what the data they're giving you.
For the example you give, I'd be explicit, and chain scopes together to build the query you want:
scope = Post
scope = scope.random if params[:scope] == 'random'
#posts = scope.find(:all, ...) # or paginate or whatever you need to do
If params[:scope] isn't 'random', this is the same as calling Post.find(), otherwise it's doing Post.random.find()
From one of the other answers, it looks like find_by_filter would do pretty much the same thing for you.
Using this pattern, you can also combine multiple scopes into the query if you needed to support things that weren't mutually exclusive
e.g.
scope = scope.only_monsters if params[:just_monsters] == 1
scope = scope.limit(params[:limit].to_i) unless params[:limit].to_i.zero?
So GETting /posts?scope=random&just_monsters=1&limit=5 will give you:
Post.random.just_monsters.limit(5).find(:all, ...)