Ruby on Rails: Accessing HTML elements from model - html

I'm trying to find a way to access html elements from my view from within my model.
I'm trying to access the the page title. On my view I have this:
<% provide(:title, 'Baseline') %>
And from my model, here is my latest attempt:
def steps
if #title == 'Baseline'
%w[sfmfa phq whoqol_bref positive_negative ]
elsif #title == 'Treatment Completion'
%w[smfa phq ]
else
%w[]
end
end
I also tried by using params[:title], but params isn't recognized in the model. This feels like a really dumb question, but I haven't been able to find a straight forward answer.
Any input would be greatly appreciated.
EDIT. Adding more detail.
As mentioned below, I'm going about this wrong. So now I'm trying to pass the correct identifier from my controller, to the model.
Currently I have pagination for one page, 'Baseline'. I'm trying to allow for pagination on 2 other pages. I simply need to be able to change the value of whats held in steps.
My old 'steps' method looked like this:
subject.rb
def steps
%w[sfmfa ...]
end
And here are what steps is used for:
def current_step
#current_step || steps.first
end
def next_step
self.current_step = steps[steps.index(current_step)+1]
end
def previous_step
self.current_step = steps[steps.index(current_step)-1]
end
def first_step?
current_step == steps.first
end
def last_step?
current_step == steps.last
end
So, I guess my new method might look something like this, where I pass the argument from the controller:
def steps(title)
if title == 'Baseline'
%w[sfmfa ...]
elsif title == 'Other'
%w[sma ...]
else
#Shouldnt get here
end
Also, here is how my view renders the steps:
<%= render "base_#{#subject.current_step}", :f => f %>

Related

Navigation with Rails gem "acts-as-taggable-on"

I want to make an Navigation with specific Tags.
These Tags are for example: HTML, CSS and Javascript.
So when i click on one of them it will show all posts with these tag.
How can i achieve that?
My code for the Navigation right now looks like this (it's in the Application.html.erb)
<%= link_to "Computer", tag_list.Computer %>
I get this Error:
undefined local variable or method `tag_list' for #<#:0x007feec764ff88>
tag_list is a local variable or method, so unless you've created it in a helper that's your first issue. The second is that called .Computer on it doesn't work because tag_list is a method that created by the gem to list all an objects tags, and calling the . (also knowing as chaining) is attempting to call a method named Computer, which doesn't exist, that should just be a string and strings have to be quoted.
So, in your layout view, you can do
= link_to "Computer", tagged_posts_url(tag: "Computer")
Then in your posts_controller.rb add an action called tagged
def tagged
if params[:tag].present?
#posts = Post.tagged_with(params[:tag])
else
#posts = Post.all
end
end
To maintain a DRY set of views, you can even tell it to render the index view since you most likely already have a list of posts, now it will look exactly the same but only contain posts with that tag. e.g.
def tagged
if params[:tag].present?
#posts = Post.tagged_with(params[:tag])
else
#posts = Post.all
end
render "index"
end
Then in your config/routes.rb add a route for your new controller action under your existing post route
resources :posts do
collection do
get "/posts/tagged", as: :tagged
end
I got it myself.
Here is the Code:
<%= link_to 'Computer', { :controller => 'posts', :action => 'index', :tag => 'Computer'} %>
The controller looks like this:
def index
if params[:tag]
#posts = Post.tagged_with(params[:tag]).order('created_at DESC')
else
#posts = Post.all.order('created_at DESC')
end
end

rails before_action on csv download

I'm using devise to authenticate users. I want them to be able to look at the page without being logged in, but if they wish to download the csv data, they must be logged in. Here's how i've set up the csv download part
respond_to do |format|
format.html{
render :layout => 'indices_show'
}
format.csv{
export_to_csv(idxp)
}
end
and here is the export_to_csv function
def export_to_csv(idxf)
cash = params[:cash]
#title = get_title(#index)
if (cash =='1')
navs = #index.navsc.from(idxf)
r = #index.returnsc.from(idxf)
else
navs = #index.navs.from(idxf)
r = #index.returns.from(idxf)
end
dates = #index.dates.from(idxf)
csv_string = CSV.generate do |csv|
csv << [#title]
csv << ["Date", "Return", "NAV"]
dates.each_with_index do |d, i|
csv << [d,r[i],navs[i]]
end
end
send_data csv_string,
:type => 'text/csv; charset=iso-8859-1; header=present',
:disposition => "attachment; filename ="+ #title +".csv"
end
At the top of this controller, I have,
before_action :authenticate_user!, only:[:export_to_csv]
but it doesn't do anything as the user is still able to download the data without being logged in. I've found a semi work around by doing this,
respond_to do |format|
format.html{
render :layout => 'indices_show'
}
format.csv{
if (user_signed_in?)
export_to_csv(idxp)
else
redirect_to new_user_session_path
end
}
end
The problem here is that once the user logs in, it redirects to the homepage. Is there a way such that when a user clicks the download button, they are forced to sign in, and upon signing in they are redirected back to that page and the data is downloaded? Thanks
The before_action isn't working because export_to_csv is not the method being called as the action - that would normally be the method your routing maps to - i.e. probably the method with your respond_to code in it. Of course, you can split the method into two separate ones and have one for html and one for csv and then have the before_action set up for the csv one. That's perhaps not so nice, especially if it's just two different viewing formats for the same data. If they're not very similar views, perhaps it would be appropriate to separate them. It depends on your app.
Another option is to use your second approach but modify it. Before the redirect_to new_user_session_path, try inserting a call to store_location_for(:user, request.request_uri), or perhaps pass request.original_url. See here for the doc/code:
https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/store_location.rb#L26
You can see that if the stored location is present, it's used in preference to the root path:
https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb#L143
Note that store_location_for seems to be a recent addition to devise. I'm actually using 3.1.x and it doesn't seem to be defined - you'd have to set the user_return_to session variable directly because that's what is checked during signin in 3.1.x.
As Tim said, export_to_csv is not the method being filtered by before_action. It is firing actions based on the controller action methods at the top, and since you are trying to limit access lower down, it has already let the user through.
I'm not sure what the action name is that you are using, so I am going to assume it's show.
You can supply a conditional to your before_action line that may limit what you were hoping for.
before_action :authenticate_user!, only: [:show], if: proc { request.csv? }

Nested strong parameters in rails - AssociationTypeMismatch MYMODEL expected, got ActionController::Parameters()

I'm rendering a model and it's children Books in JSON like so:
{"id":2,"complete":false,"private":false, "books" [{ "id":2,"name":"Some Book"},.....
I then come to update this model by passing the same JSON back to my controller and I get the following error:
ActiveRecord::AssociationTypeMismatch (Book (#2245089560) expected, got ActionController::Parameters(#2153445460))
In my controller I'm using the following to update:
#project.update_attributes!(project_params)
private
def project_params
params.permit(:id, { books: [:id] } )
end
No matter which attributes I whitelist in permit I can't seem to save the child model.
Am I missing something obvious?
Update - another example:
Controller:
def create
#model = Model.new(model_params)
end
def model_params
params.fetch(:model, {}).permit(:child_model => [:name, :other])
end
Request:
post 'api.address/model', :model => { :child_model => { :name => "some name" } }
Model:
accepts_nested_attributes_for :child_model
Error:
expected ChildModel, got ActionController::Parameters
Tried this method to no avail: http://www.rubyexperiments.com/using-strong-parameters-with-nested-forms/
Are you using accepts_nested_attributes_for :books on your project model? If so, instead of "books", the key should be "books_attributes".
def project_params
params.permit(:id, :complete, :false, :private, books_attributes: [:id, :name])
end
I'm using Angular.js & Rails & Rails serializer, and this worked for me:
Model:
has_many :features
accepts_nested_attributes_for :features
ModelSerializer:
has_many :features, root: :features_attributes
Controller:
params.permit features_attributes: [:id, :enabled]
AngularJS:
ng-repeat="feature in model.features_attributes track by feature.id
My solution to this using ember.js was setting the books_attributes mannualy.
In controller:
def project_params
params[:project][:books_attributes] = params[:project][:books_or_whatever_name_relationships_have] if params[:project][:books_or_whatever_name_relationships_have]
params.require(:project).permit(:attr1, :attr2,...., books_attributes: [:book_attr1, :book_attr2, ....])
end
So rails checks and filters the nested attributes as it expected them to come
This worked for me. My parent model was an Artist and the child model was a Url.
class ArtistsController < ApplicationController
def update
artist = Artist.find(params[:id].to_i)
artist.update_attributes(artist_params)
render json: artist
end
private
def artist_params
remap_urls(params.permit(:name, :description, urls: [:id, :url, :title, :_destroy]))
end
def remap_urls(hash)
urls = hash[:urls]
return hash unless urls
hash.reject{|k,v| k == 'urls' }.merge(:urls_attributes => urls)
end
end
class Artist < ActiveRecord::Base
has_many :urls, dependent: :destroy
accepts_nested_attributes_for :urls, allow_destroy: true
end
class Url < ActiveRecord::Base
belongs_to :artist
end
... and in coffeescript (to handle deletions):
#ArtistCtrl = ($scope, $routeParams, $location, API) ->
$scope.destroyUrls = []
$scope.update = (artist) ->
artist.urls.push({id: id, _destroy: true}) for id in $scope.destroyUrls
artist.$update(redirectToShow, artistError)
$scope.deleteURL = (artist,url) ->
artist.urls.splice(artist.urls.indexOf(url),1)
$scope.destroyUrls.push(url.id)
Something is missing from all of the answers, which is the inputs for fields_for in the form.
The form works if you do this:
f.fields_for #model.submodel do ..
However, the form is sent as model[submodel], but that's what causes the error others have mentioned in their answers. If you try to do model.update(model_params), Rails will raise an error that it's expecting a Submodel type.
To fix this, make sure you follow the :name, value format:
f.fields_for :submodel, #model.submodel do ...
Then in the controller, make sure you put _attributes on your params:
def model_params
params.require(:model).permit(submodel_attributes: [:field])
end
Now the save, update, etc. will work fine.
Wasted several days trying to figure out how to use accepts_nested_attributes with Angular, and the issue is always the same: Rails whitelist will not allow the variables into the params hash. I've tried every single different whitelisting syntax that everyone said on SO and other blogs, tried using :inverse, tried using habtm and mas_many_through, tried manually rolling my own solution but that wont work if the whitelist wont allow params through, tried doing what http://guides.rubyonrails.org says about 'Outside the Scope of Strong Parameters', tried removing whitelisting all together which isnt really an option but it causes other problems anyways. Not sure why rails 4 strong parameter whitelisting wont allow arbitrary data thru, thats a huge problem especially if accepts_nested_attributes doesn't work either.... I guess we are left to just create/delete all associations on a separate page/form/controller and look like an idiot making my end users use several forms/pages to do something that should be easily doable on 1 page with 1 form. Ya know, usually I expect Angular to screw me, but this time Angular worked quite well and it was actually Rails 4 that screwed me twice on 1 issue that should be very straightforward.

Changing value of Ruby Variable in Html.erb

I have the following code in my controller:
class TestController < ApplicationController
##a = 1
def index
#temp = connection.execute("select test_id from mastertest limit #{##a}, 5;")
end
And I have the following code in my View(Html.erb) File:
<button type="submit" value="Next" form="submit_form">NEXT</button>
<form id="submit_form">
<% ##a = ##a + 1 %>
<table>
<% #temp.each do |row| %>
<tr><td><%= row[0] %></td></tr>
<% end %>
</table>
</form>
So basically I am trying to change the value of the class variable ##a on clicking the Next button. But it does not change the value of ##aa. Can someone help me how to do that.
Did you try using helper method?
module ApplicationHelper
##a = 1
def increment_a
##a = ##a + 1
end
end
and in your view just call;
<% increment_a %>
Not that the ## variable is a class variable and it's shared among all instances of the that class. So define that class somewhere in the ApplicationHelper class and then it will be shared and can be accessed in the Controllers and views.
In all cases I highly discourage using class variables in such a way and recommend that you ind another way to share data/variables between view / controller. Maybe use another supporting class or store values in the database.
If you want to alter a Rails variable on a form submission, you should put the code to do it in the action which processes the form.
As you've written it, I believe the variable will get set when the template containing the form is rendered.
I also vaguely recall that there's some special considerations about class variables in Rails apps. You should look into that and make sure you're using a technique that won't cause any unexpected results.
Ok I managed to fix this:
Ruby has something called a global variable which can be declared like this :
$a = 1
Using $a everywhere retains its value in the controller and the view as well.

rails page titles

I don't like the way rails does page titles by default (just uses the controller name), so I'm working on a new way of doing it like so:
application controller:
def page_title
"Default Title Here"
end
posts controller:
def page_title
"Awesome Posts"
end
application layout:
<title><%=controller.page_title%></title>
It works well because if I don't have a page_title method in whatever controller I'm currently using it falls back to the default in the application controller. But what if in my users controller I want it to return "Signup" for the "new" action, but fall back for any other action? Is there a way to do that?
Secondly, does anyone else have any other ways of doing page titles in rails?
I disagree with the other answers, I believe the title shouldn't be set per action, but rather within the view itself. Keep the view logic within the view and the controller logic within the controller.
Inside your application_helper.rb add:
def title(page_title)
content_for(:title) { page_title }
end
Then to insert it into your <title>:
<title><%= content_for?(:title) ? content_for(:title) : "Default Title" %></title>
So when you are in your views, you have access to all instance variables set from the controller and you can set it there. It keeps the clutter out of the controller as well.
<%- title "Reading #{#post.name}" %>
I like to put a catchall, default title in my layout that can be overridden from an action by setting #title:
<title><%= #title || "Default Title Here" %></title>
Then you can generate a title in your action:
def show
#post = Post.find_by_id params[:id]
#title = "tybro's blog: #{#post.title}"
end
I would do this:
# Application Controller
before_filter :set_page_title
private
def set_page_title
#page_title = "Default Title"
end
overwrite it in your other controllers
# Users Controller
before_filter :set_page_title
def new # in Users controller
...
#page_title = "Sign up"
...
end
private
def set_page_title
#page_title = "Users"
end
In your view:
<title><%= h #page_title %></title>
Have a look at Ryan Bates (ryanb from railscasts.com) nifty layout. He has a method in there that does what you are looking for. It's similar to Garrett's way, only he moves the default values in the helper too. Check out the helper.rb file and how he link textuses it.
You can install is as gem (and use the other nice features) or just use his way of doing it. You only need to pass the title value to title in each view (or let it fall to the default) and there you go. I'm with Garrett to put these things in the view.
Layout template
In your layout (e.g. application.html.erb) add:
<title><%= content_for(:page_title) || "Fallback title" %></title>
Page template
In the page template you'd like to a specify a title from:
<%- content_for(:page_title, "Specific page title") %>
class ApplicationController < ActionController::Base
before_action :set_page_title
private
def set_page_title
#page_title = t(".page_title", default: '').presence || t("#{controller_name}.page_title", default: '').presence || controller_name.titleize
end
end
I recently started taking this approach then outputting #page_title in the layout. It seems to work quite nicely for me.