Passing params in Rails helper class - ruby-on-rails

I am trying to refactor my Rails helpers and move breadcrumbs and navigation menu logic into separate classes. But in these classes I don't have access to params, cookies hashes etc. I think that passing params on and on between different classes is a bad idea. How can I avoid that?
For example I have:
module NavigationHelper
def nav_item(name, path, inactive = false)
NavItem.new(params, name, path, inactive ).render
end
class NavItem
include ActionView::Helpers
include Haml::Helpers
def initialize(params, name, path, inactive )
init_haml_helpers
#params = params
#name = name
#path = path
#inactive = inactive
end
def render
capture_haml do
haml_tag :li, item_class do
haml_concat link_to #name, #path
end
end
end
def item_class
klass = {class: 'active'} if active?
klass = {class: 'inactive'} if #inactive
klass
end
# Class of the current page
def active?
slug = #path.gsub /\//, ''
#params[:page] == slug || #params[:category] == slug
end
end
end

I don't think Rails provide any mechanism to access Params out of ActionPack. The way you have done is seems correct to me. You have to pass on params, cookies atleast once to initialize your classes.

Related

Using strong params when assigning model attributes in controller

Hi i'm running into an issue with my code where i'm not sure where to use strong params. In this case I have a document object being set with a mix of preset values and values coming from my form for example.
class DocumentsController < ApplicationController
def add_document
document_name = params[:document_name]
document_parent_id = params[:doc_parent_id]
#document = Document.new(name: document_name, parent_id: document_parent_id, document_owner_id: current_user_id, created_by: current_user.name)
#document.save
#do flash stuff here
end
So the form is only submitting the document name and document parent id through the params hash. Should these two values be whitelisted using strong params? And If so how can I use strong params to create the new document with the other values that aren't coming from my form.
Thanks.
1/ Yes it should be whitelisted.
def add_document
# stuff
#document = Document.new(document_params.merge(
document_owner_id: current_user_id,
created_by: current_user.name
))
# stuff
end
def document_params
params.require(:document).permit(:name, :parent_id)
end
2/ To submit not from form, you just need to submit nested attribute document inside params alongside with other params:
{ document: { name: '<Name>', parent_id: '<Id>' }, other_params: '...' }
class DocumentsController < ApplicationController
def add_document
#document = Document.new document_params.merge(document_owner_id: current_user_id, created_by: current_user.name)
#document.save
end
private
def document_params
params.permit(:document_name, :doc_parent_id)
end
end
Your code really could do with improving a lot.
Firstly, Rails 4+ convention is to have a "top level" param value of the model (in your case document):
params: {
document: {
document_name: "x",
doc_parent_id: "y"
}
}
This would allow you to call the strong params method properly:
def document_params
params.require(:document).permit(:document_name, :doc_parent_id)
end
The way to achieve this is to use form_for (which should be used in conjunction with a RESTful controller):
#app/views/documents/new.html.erb
<%= form_for #document do |f| %>
<%= f.text_field :document_name %>
<%= f.submit %>
<% end %>
#app/controllers/documents_controller.rb
class DocumentsController < ApplicationController
def new
#document = Document.new
end
def create
#document = Document.new document_params
#document.save
end
end
--
Finally, you also need to make sure your model attribute names work well.
You're currently using document_name as an attribute name. If it were my application, I'd call it name, allowing you to call #document.name in the future.
The same for your other attributes:
document_name -> "name"
doc_parent_id -> "parent_id"
document_owner_id -> "owner_id"

undefined method `permit' for "titleofcoolstuff":String

I am just trying to get two parameters from a view to my controller. I'm using Rails 4.2.x and strong params are killing me.
One param, :query, resolves correctly. However the second param, :location, throws the error in the questions title. I have Googled the issue but everyone's scenario seems to be different and their solutions (relatively) unique.
The view in question is index.html.erb which only contains a simple search form.
<%= form_tag("/searches", action: "create", method: "post") do %>
<div>Job Title</div>
<%= text_field_tag(:query) %>
<div>Location</div>
<%= text_field_tag(:location) %>
<%= submit_tag("Go") %>
<% end %>
The controller in question is searches_controller.rb.
class SearchesController < ApplicationController
def index
binding.pry
end
def show
binding.pry
end
def update
end
def create
#query = search_params["query"].to_s || nil
#location = search_params[:location].to_s || nil
binding.pry
end
def delete
end
private
def search_params
params.require(:query).permit(:location)
end
end
The stack trace points to the search_params method, and shows me that I have the following params in the controller
{
"utf8"=>"✓",
"authenticity_token"=>"DEcTwT/NnSY3S3n25zZGXD+KRZcsRkWj9bmN57AMNivFbMXwHF5Vf/psgzSMkZPBa+OWJgafXYGdW+o5KN3xxg==",
"query"=>"titleofcoolstuff",
"location"=>"milwauke",
"commit"=>"Go"
}
What am I missing?
Strong parameters is for providing a hash of attributes, for example:
<%= form_for #user do |f| %>
## form
<% end %>
This may send parameters like this:
"user" => { "name"=> "Your Name", "age" => "23", "location" => "USA" }
Strong parameters in this case would be instructing rails to process the users hash of attributes and specifically these attributes, like this:
params.require(:user).permit(:name, :age, :location)
In your case, you are passing in individual parameters (not hashes of attributes), so if you want to grab them, you grab them explicitly:
def create
#query = params[:query].to_s || nil
#location = params[:location].to_s || nil
#do something
end
No need for strong parameters to whitelist model attributes here. Hope this helps.
In your case
"query"=>"titleofcoolstuff",
"location"=>"milwauke",
"commit"=>"Go"
since your data are not wrapped with any keys (they are at the root) so you can simply access them using like params[:query].
Whitelisting/Strong params
We need to whitelist params only for mass assignment. like #user.update(user_params) Here, unless the params sent by users in user_params are whitelisted i.e. permitted using .permit method; the update method will throw an exception ActiveModel::ForbiddenAttributes.
In your case since your not updating anything you do not need to create strong params for it.
def create
#query = params["query"].to_s || nil
#location = params[:location].to_s || nil
binding.pry
end
If you are gonna do mass assignment in future you have to whitelist your params
For more info see https://cbabhusal.wordpress.com/2015/10/02/rails-strong-params-whilisting-params-implementation-details/

Passing a variable between actions within same controller Rails

My show action:
def show
# Multiple keywords
if current_user.admin?
#integration = Integration.find(params[:id])
else
#integration = current_user.integrations.find(params[:id])
end
#q = #integration.profiles.search(search_params)
#profiles = #q.result.where(found: true).select("profiles.*").group("profiles.id, profiles.email").includes(:integration_profiles).order("CAST( translate(meta_data -> '#{params[:sort_by]}', ',', '') AS INT) DESC NULLS LAST").page(params[:page]).per_page(20)
#profiles = #profiles.limit(params[:limit]) if params[:limit]
end
There can be many different filters taking place in here whether with Ransacker, with the params[:limit] or others. At the end I have a subset of profiles.
Now I want to tag all these profiles that are a result of the search query.
Profiles model:
def self.tagging_profiles
#Some code
end
I'd like to create an action within the same controller as the show that will execute the self.tagging_profiles function on the #profiles from the show action given those profiles have been filtered down.
def tagging
#profiles.tagging_profiles
end
I want the user to be able to make a search query, have profiles in the view then if satisfied tag all of them, so there would be a need of a form
UPDATE:
This is how I got around it, don't know how clean it is but here:
def show
# Multiple keywords
if current_user.admin?
#integration = Integration.find(params[:id])
else
#integration = current_user.integrations.find(params[:id])
end
#q = #integration.profiles.search(search_params)
#profiles = #q.result.where(found: true).select("profiles.*").group("profiles.id, profiles.email").includes(:integration_profiles).order("CAST( translate(meta_data -> '#{params[:sort_by]}', ',', '') AS INT) DESC NULLS LAST").page(params[:page]).per_page(20)
#profiles = #profiles.limit(params[:limit]) if params[:limit]
tag_profiles(params[:tag_names]) if params[:tag_names]
end
private
def tag_profiles(names)
#profiles.tagging_profiles
end
In my view, I created a form calling to self:
<%= form_tag(params.merge( :controller => "integrations", :action => "show" ), method: :get) do %>
<%= text_field_tag :tag_names %>
<%= submit_tag "Search", class: "btn btn-default"%>
<% end %>
Is this the best way to do it?
Rails public controller actions correspond always to a http request. But here there is just no need for 2 http requests. A simple solution would be just creating to private controllers methods filter_profiles(params) and tag_profiles(profiles) and just call them sequentially.
You can also extract this problem entirely to a ServiceObject, like this:
class ProfileTagger
attr_reader :search_params
def initialize(search_params)
#search_params = search_params
end
def perform
search
tag
end
def tag
#tag found profiles
end
def search
#profiles = #do the search
end
end
As processing 30,000 records is a time consuming operation, it would make sence to perform it outside of the rails request in background. This structure will allow you to delegate this operation to a sidekiq or delayed_job worker with ease
Instance Variables
If you want to "share" variable data between controller actions, you'll want to look at the role #instance variables play.
An instance of a class means that when you send a request, you'll have access to the #instance variable as long as you're within that instance of the class, I.E:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
before_action :create_your_var
def your_controller
puts #var
end
private
def create_your_var
#var = "Hello World"
end
end
This means if you wish to use the data within your controller, I would just set #instance variables, which you will then be able to access with as many different actions as you wish
--
Instance Methods
The difference will be through how you call those actions -
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def action
#-> your request resolves here
method #-> calls the relevant instance method
end
private
def method
#-> this can be called within the instance of the class
end
end

Access a controller's instance variable from a block using instance_eval

I'm making a breadcrumb module for my Ruby on Rails application, but I wanted a specific syntax - which I thought was good looking and more intuitive for Rails developers.
Here's the deal:
class WelcomeController < ApplicationController
breadcrumb_for :index, :text => 'Home', :href => -> { root_path }
def index
end
end
See, it's neat.
You can safely ignore the everything else but that proc - what I assign to the :href key.
I use instance_eval so that when the proc is evaluated it has access to the root_path helper.
And it worked. The example above is okay. BUT then I wanted to use an instance variable and that didn't work.
Like this:
class WelcomeController < ApplicationController
breadcrumb_for :index, :text => 'Home', :href => -> { #path }
def index
#path = root_path
end
end
Now, in that proc context #path is nil.
What should I do so I can access the instance variables from the block ?
Below is all the code of my module. Note that when I "process" the blocks and use instance_eval (aka call my module's #breadcrumb) the action should already be evaluated so the instance variable #path should already exist.
module Breadcrumb
extend ActiveSupport::Concern
included do
cattr_accessor(:_breadcrumb) { [] }
helper_method :breadcrumb
def self.breadcrumb_for(*args)
options = args.pop
_breadcrumb.push([args, options])
end
end
def breadcrumb
#breadcrumb ||= self._breadcrumb.map do |item|
puts item
if item[0].include?(params[:action]) || item[0][0] == '*'
text, href = item[1].values_at(:text, :href)
if text.respond_to?(:call)
text = instance_eval(&text)
end
if href.respond_to?(:call)
href = instance_eval(&href)
end
[text, href]
end
end
end
end
Oh no. I'm ashamed to say but it was my mistake. The code above works just fine, I was using different variable names in my application, not shown in the excerpt I used in the question.
Thanks anyway, I'll left it here for reference.

Semantic-Menu root bahaviour

require 'rubygems'
require 'action_view'
require 'active_support'
class MenuItem
include ActionView::Helpers::TagHelper,
ActionView::Helpers::UrlHelper
attr_accessor :children, :link
cattr_accessor :request
def initialize(title, link, level, link_opts={})
#title, #link, #level, #link_opts = title, link, level, link_opts
#children = []
end
def add(title, link, link_opts={}, &block)
returning(MenuItem.new(title, link, #level +1, link_opts)) do |adding|
#children << adding
yield adding if block_given?
end
end
def to_s
content_tag(:li, content_tag(:div, link_to(#title, #link, #link_opts), :class => "menu_header_level_"+#level.to_s) + child_output, ({:class => 'active'} if active?)).html_safe
end
def level_class
"menu_level_#{#level}"
end
def child_output
children.empty? ? '' : content_tag(:ul, #children.collect(&:to_s).join.html_safe, :class => level_class)
end
def active?
children.any?(&:active?) || on_current_page?
end
def on_current_page?
# set it for current_page? defined in UrlHelper
# current_page?(#link)
false
end
# def request
# ##request
# end
end
class SemanticMenu < MenuItem
def initialize(rq, opts={},&block)
##request = rq
#opts = {:class => 'menu'}.merge opts
#level = 0
#children = []
yield self if block_given?
end
def to_s
content_tag(:ul, #children.collect(&:to_s).join.html_safe, #opts).html_safe
end
end
Hello. I am trying to change the behaviour of the Semantic-Menu root. When I click one of the roots, the menu drops down and displays all the children. What I would like is happen is when I click, it goes to a default page and then display the children. Semantic-menu seems to allow links only to lower levels and not the main ones. Roots links only work when they don't have children.
The code below is the one that is in the plug-in in Ruby. and I think is the one that needs to be modified. There the html code but I don't think it has to do with it.
Can you please tell me what need to be added in other to make to father trigger their links?
Thank you.
I don't know the direct answer to your question, but SemanticMenu seems outdated.
Check out the SimpleNavigation gem: https://github.com/andi/simple-navigation/wiki

Resources