I wonder how can I validate a slug (from user input) against all method names of a controller (not necessarily the ones in self) other than hardcode it, so say tag/tomato is valid, but /tag/all is not, because there is an all method in controller Tag? Using reflection?
Or there is a better practice?
Sounds like you really want to protect against routing conflicts and that is only loosely connected to the method names in your controller.
You can get all the routes at run time from
Rails.application.routes.routes
That gives you an Array of ActionDispatch::Routing::Route instances. Then, to get the GET routes:
gettable = Rails.application.routes.routes.select do |r|
r.verb == 'GET' || r.verb == '' # Watch out for "no verb" -> "all verbs"
end
and from there you can extract the paths and check that your tag doesn't match any of them:
paths = Rails.application.routes.routes.
select { |r| r.verb == 'GET' || r.verb == '' }.
map { |r| r.path }
That leaves you with a list of /this/:that(.:format) style paths in paths.
Once all of that is in place, you'll want an application initializer to check that you haven't added any routes to the /tag/ namespace that happen to match the current state of the tag database; otherwise, conflicts can creep in during development.
That should convince you that you're better off keeping the normal routes for creating, viewing, and such in a separate namespace from your human/SEO friendly /tag/pancakes routes. You could leave the usual ones in /tag but move the friendly ones to /taxonomy/, /category/, or something similar.
You can get the list of methods defined in your controller like this:
TagController.instance_methods(false)
Note that by passing false as an argument to instance_methods, you get the list of methods that are not inherited.
Related
Hi I am trying to extract a value from a Netsuite hash inside custom fields, and some others, which typically look like this - `
"custbody_delivery_ticket_number"=>
{
"script_id"=>"custbody_delivery_ticket_number",
"internal_id"=>"2701",
"type"=>"platformCore:DateCustomFieldRef",
"attributes"=> {
"value"=>"123abc"
}
}` and want the value of it inside of attributes.
Have tried many different ways, but one in particular -
delivery_ticket_number: "#{netsuite_sales_orders.custom_field_list.custom_fields.select['custbody_nef_meter_ticket_number']['attributes']['value']}",
throws error for class Enumerator, NoMethodError: undefined method `[]' for #Enumerator:0x00005589ec778730 which indicates may be getting close, but doing something wrong.
If anyone has any idea how to get values from these kind of hashes?
(Am told by the system admin that it is the correct custbody identifier)
Many Thanks
Eventually fixed this, grabbing Netsuite custom fields with a select of script_id by name,and map as below:
delivery_date:netsuite_sales_order.custom_fields_list.custom_fields.select { |field| field.script_id == 'custbody_delivery_date' }.map { |field| field.value }.first
First selecting the script_id by unique name, then mapping to the value. Was able to get any custom field like this, preferable as they can move and might not have the same index if use an index to grab them, fetching an incorrect value. This way ensures getting the correct data even if the item is moved up or down in the custom fields list.
Thanks for everyones help!
I'm building a site with users in all 50 states. We need to display information for each user that is specific to their situation, e.g., the number of events they completed in that state. Each state's view (a partial) displays state-specific information and, therefore, relies upon state-specific calculations in a state-specific model. We'd like to do something similar to this:
##{user.state} = #{user.state.capitalize}.new(current_user)
in the users_controller instead of
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
.... [and the remaining 49 states]
#wisconsin = Wisconsin.new(current_user) if (#user.state == 'wisconsin')
to trigger the Illinois.rb model and, in turn, drive the view defined in the users_controller by
def user_state_view
#user = current_user
#events = Event.all
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
end
I'm struggling to find a better way to do this / refactor it. Thanks!
I would avoid dynamically defining instance variables if you can help it. It can be done with instance_variable_set but it's unnecessary. There's no reason you need to define the variable as #illinois instead of just #user_state or something like that. Here is one way to do it.
First make a static list of states:
def states
%{wisconsin arkansas new_york etc}
end
then make a dictionary which maps those states to their classes:
def state_classes
states.reduce({}) do |memo, state|
memo[state] = state.camelize.constantize
memo
end
end
# = { 'illinois' => Illinois, 'wisconsin' => Wisconsin, 'new_york' => NewYork, etc }
It's important that you hard-code a list of state identifiers somewhere, because it's not a good practice to pass arbitrary values to contantize.
Then instantiating the correct class is a breeze:
#user_state = state_classes[#user.state].new(current_user)
there are definitely other ways to do this (for example, it could be added on the model layer instead)
How do I create an object if one is not found? This is the query I was running:
#event_object = #event_entry.event_objects.find_all_by_plantype('dog')
and I was trying this:
#event_object = EventObject.new unless #event_entry.event_objects.find_all_by_plantype('dog')
but that does not seem to work. I know I'm missing something very simple like normal :( Thanks for any help!!! :)
find_all style methods return an array of matching records. That is an empty array if no matching records are found. And an empty is truthy. Which means:
arr = []
if arr
puts 'arr is considered turthy!' # this line will execute
end
Also, the dynamic finder methods (like find_by_whatever) are officially depreacted So you shouldn't be using them.
You probably want something more like:
#event_object = #event_entry.event_objects.where(plantype: 'dog').first || EventObject.new
But you can also configure the event object better, since you obviously want it to belong to #event_entry.
#event_object = #event_entry.event_objects.where(plantype: 'dog').first
#event_object ||= #event_entry.event_objects.build(plantype: dog)
In this last example, we try to find an existing object by getting an array of matching records and asking for the first item. If there are no items, #event_object will be nil.
Then we use the ||= operator that says "assign the value on the right if this is currently set to a falsy value". And nil is falsy. So if it's nil we can build the object form the association it should belong to. And we can preset it's attributes while we are at it.
Why not use built in query methods like find_or_create_by or find_or_initialize_by
#event_object = #event_entry.event_objects.find_or_create_by(plantype:'dog')
This will find an #event_entry.event_object with plantype = 'dog' if one does not exist it will then create one instead.
find_or_initialize_by is probably more what you want as it will leave #event_object in an unsaved state with just the association and plantype set
#event_object = #event_entry.event_objects.find_or_initialize_by(plantype:'dog')
This assumes you are looking for a single event_object as it will return the first one it finds with plantype = 'dog'. If more than 1 event_object can have the plantype ='dog' within the #event_entry scope then this might not be the best solution but it seems to fit with your description.
I am just wondering if it is possible to have a condition that passes information to the request body once it is complete, I doubt conditions can do it and are the right place even if they could, because it implies they are to do conditional logic, however the authorisation example also redirects so it has a blur of concerns... an example would be something like:
set(:get_model) { |body| { send_to_request_body(Model.new(body)) } }
get '/something', :get_model => request.body.data do
return "model called #{#model.name}"
end
The above is all psudocode so sorry for any syntax/spelling mistakes, but the idea is I can have a condition which fetches the model and puts it into some local variable for the body to use, or do a halt with an error or something.
I am sure filters (before/after) would be a better way to do this if it can be done, however from what I have seen I would need to set that up per route, whereas with a condition I would only need to have it as an option on the request.
An example with before would be:
before '/something' do
#model = Model.new(request.body.data)
end
get '/something' do
return "model called #{#model.name}"
end
This is great, but lets say I now had 20 routes, and 18 of them needed these models creating, I would need to basically duplicate the before filter for all 18 of them, and write the same model logic for them all, which is why I am trying to find a better way to re-use this functionality. If I could do a catch-all Before filter which was able to check to see if the given route had an option set, then that could possibly work, but not sure if you can do that.
In ASP MVC you could do this sort of thing with filters, which is what I am ideally after, some way to configure certain routes (at the route definition) to do some work before hand and pass it into the calling block.
Conditions can set instance variables and modify the params hash. For an example, see the built-in user_agent condition.
set(:get_model) { |body| condition { #model = Model.new(body) } }
get '/something', :get_model => something do
"model called #{#model.name}"
end
You should be aware that request is not available at that point, though.
Sinatra has support for before and after filters:
before do
#note = 'Hi!'
request.path_info = '/foo/bar/baz'
end
get '/foo/*' do
#note #=> 'Hi!'
params[:splat] #=> 'bar/baz'
end
after '/create/:slug' do |slug|
session[:last_slug] = slug
end
Part 1:
I have a call to layout(:default){|path,wish| wish !~ /rss|atom|json/} but requests to /foo/bar.json seem to think wish is html and uses the layout anyway. How can I fix this?
Part 2:
I want to route /path/to/file.ext so that it calls the method to on the controller mapped to /path and uses ext when formulating the return. Is there a better (more elegant) way to do this than passing the 'file.ext' to the to method, parsing it, and doing cases? This question would have been more succinct if I had written, how does one do REST with Ramaze? There appears to be a Google Groups answer to this one, but I can't access it for some reason.
class ToController < Controller
map '/path/to'
provide( :json, :type => "application/json") { |action, val| val.to_json }
def bar
#barInfo = {name: "Fonzie's", poison: "milk"}
end
end
This controller returns plain JSON when you request /path/to/bar.json and uses the layout+view wrapping when you request /path/to/bar (Ramaze has no default layout setting, the layout in this example comes from the Controller parent class).