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
Related
I have a simple structure of "A has_many B has_many C"
If I go into Rails console and do something like A.first.Bs.first.C.create() it'll create without an issue, however, if I use the API (or even Seeds actually) and so something like POST to /api/v1/a/1/b with the below create, I will always get rejected due to "Must belong to A" - Basically meaning it's trying to save as a.id = null.
A = Campaign. B = Party for the below snippet.
def create
#campaign = Campaign.find_by_id(params[:campaign_id])
if #campaign.user_id == current_user.id
#party = Party.new(party_params)
# #party.campaign_id = params[:campaign_id]
if #party.save!
render status: 201, json: {
message: "Successfully saved the party!",
party: #party,
user: current_user
}
else
render status: 404, json: {
message: "Something went wrong: Check line 27 of Party Controller"
}
end
end
end
The line I have commented out where I manually assigned #party.campaign_id resolved the error, but I am curious why it doesn't automatically pull from the information? Do route resources not function the same way as a Campaign.first.parties.create would?
Welcome any revision to this create method; It feels bulky, and likely not secure at all presently.
(Note #campaign.user_id == current_user.id is kind of a generic catch in case someone is trying to update someone else's campaign. I will likely re-visit this logic to make it more secure.)
Rails does not find anything automatically basing on routes, you need to do it by yourself.
In this case you can either assign id basing on params (as you did in the comment) or build Party as an element of Campaign.parties association
#campaign = Campaign.find_by_id(params[:campaign_id])
#party = #campaign.parties.new(party_params)
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)
I am new to Ruby, so let me describe the context of my problem first:
I have a json as input which has the following key / value pair:
{
"service": "update"
}
The value has many different values for example: insert,delete etc.
Next there is a method x which handles the different requests:
def x(input)
case input[:service]
services = GenericService.new
when "update"
result = services.service(UpdateService.new,input)
when "insert"
result = services.service(InsertService.new,input)
when "delete"
result = services.service(DeleteService.new,input)
....
....
else
raise "Unknown service"
end
puts JSON.pretty_generate(result)
end
What is bothering me is that I still need to use a switch statement to check the String values (reminds me of 'instance of' ugh..). Is there a cleaner way (not need to use a switch)?
Finally I tried to search for an answer to my question and did not succeed, if however I missed it feel free to comment the related question.
Update: I was thinking to maybe cast the string to the related class name as follows: How do I create a class instance from a string name in ruby? and then call result = services.services(x.constantize.new,input) , then the class names ofcourse needs to match the input of the json.
You can try something like:
def x(input)
service_class_name = "#{input[:service].capitalize}Service"
service_class = Kernel.const_get(service_class_name)
service_class.new(input).process
end
In addition you might want to check if this is a valid Service class name at all.
I don't understand why you want to pass the service to GenericService this seems strange. let the service do it's job.
If you're trying to instatiate a class by it's name you're actually speaking about Reflection rather than Polymorphism.
In Ruby you can achieve this in this way:
byName = Object.const_get('YourClassName')
or if you are in a Rails app
byName= 'YourClassName'.constantize
Hope this helps
Just first thoughts, but you can do:
eval(services.service("#{input[:service].capitalize}Service.new, #{input})") if valid_service? input[:service]
def valid_service?
w%(delete update insert).include? input[:service]
end
As folks will no doubt shout, eval needs to be used with alot of care
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.
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).