Best practice for having the result of a same query in all routes - phoenix-framework

I would like to be able to pass data for a destak popup I have that can be shown on every route/page of my application. The data is on the database and thus I use a query to get it and pass it in a controller to a specific page:
def admin(conn, _params, locale) do
destaks = Data.listAll(query)
render(conn, "admin.html", destaks: destaks)
end
What's the best way of have it available for all pages, while making sure that when I update that data in the database it is reflected automatically on all pages?

You can use a custom Plug function for this. Here is an example:
1) Define your plug function somewhere (for my example I put this right into router.ex). The first argument is the connection that we will be adding our data to, and we don't need the second argument in this case:
def database_thing(conn, _) do
# This is where you get things from the database
data_from_a_query = ["these", "will", "be", "from", "your", "query"]
Plug.Conn.assign(conn, :values_from_database, data_from_a_query)
end
2) Add plug function to a pipeline in router.ex, you can add it to an existing pipeline or create a new one:
# Example of adding to an existing pipeline
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :database_thing # add it
end
# Example of creating a new pipeline
pipeline :everytime do
plug :database_thing
end
3) Make sure that your pipeline is in your scope. If you added it to an existing pipeline you shouldn't have to do much. If you created a new pipeline then you have to add it to the scope in router.ex:
scope "/", MyPhoenixApp do
pipe_through [:browser, :everytime]
resources "/users", UserController
end
4) Access the value in the controller. If you look at step one, you can see that we are assigning the data with the key :values_from_database. To access that data, you would do the following in your controller functions:
def index(conn, _params) do
IO.inspect(conn.assigns.values_from_database)
# ...
end
5) Access the value in the template. The conn is passed through the controller functions to the templates, so depending on what you are trying you may not need to access the value in the controller functions at all, and just use it directly in the template:
<%= #conn.assigns.values_from_database %>

Related

rails string substitution or similar solution in controller

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)

Can I add params values to a hash?

I have a User model. I also have a form_for(#user...) form. This form spans 3 partials. In order for every partial to remember values I use the following command inside my create action in my UsersController:
session[:user_params].deep_merge!(params[:user]) if params[:user]
This way every partial adds params[:user] to session[:user_params]. I also have other form values stored inside the params hash which are not part of the User model. Is there a command which would allow me to add all single params values (not just the :user hash) to the session[:user_params] hash without adding every single value one by one like this:
session[:num_children] = params[:num_children] if params[:num_children]
...etc...
Try:
params.each {|key,value| session.deep_merge!(key=>value)}

Sharing data between Sinatra condition and request block

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

Excluding method names when validating a slug in Ruby on Rails?

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.

Intermediate Ramaze Routing Help Please

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).

Resources