Hi I'm hacking around Rails the last 8 months so don't truly understand a number of things. I've seen and used helper methods that set the current_user etc. and am now trying to replicate something similar.
I'm trying to set a global value in the application controller for the current company the user is in. I know I'm doing it wrong but I can't quite figure out how to solve it or even if I'm approaching it correctly. I want it so that when the user clicks on a company, the global variable is set to the id of the company they are in. Then in any other sub-model, if I want info on the company, I use the global variable to retrieve the company object with that id.
The code that's causing the problem is in my navbar in application.html.erb
<li><%= link_to "Company", company_path, :method => :get %></li>
This works when I'm using the companies controller etc. But when I try use any other controller I'm getting the error
ActionController::UrlGenerationError in Employees#index
No route matches {:action=>"show", :controller=>"companies"} missing required keys: [:id]
which as I understand it means it can't render the url because no company id parameter is being passed?
I was trying to hack a helper method I used in another project (for getting the current_user) but have realised that it uses the session to extract the user.id to return a user object. Here's my attempt at the helper method in application_controller.rb
def current_company
#company = Company.find_by_id(params[:id])
end
I'm not able to get the current company from the session so my method above is useless unless the company id is being passed as a parameter.
I've thought about passing the company id on every sub-model method but
I'm not 100% sure how to do this
It doesn't sound very efficient
So my question is am I approaching this correctly? What's the optimal way to do it? Can I create a helper method that stores a global company id variable that gets set once a user accesses a company and can then be retrieved by other models?
I probably haven't explained it too well so let me know if you need clarification or more info. Thanks for looking.
Edit 1
Made the changes suggested by Ruby Racer and now I have:
application.html.erb
<%unless current_page?(root_path)||current_page?(companies_path)||current_company.nil? %>
<li><%= link_to "Company", company_path, :method => :get %></li>
This is not displaying the link in the navbar, I presume because current_company is nil (the other two unless statements were fine before I added current_company.nil?
I'm setting the current_company in
companies_controller.rb
before_action :set_company, only: [:show, :edit, :update, :destroy, :company_home]
def company_home
current_company = #company
respond_with(#company)
end
application_controller.rb
def current_company=(company)
session[:current_company] = company.id
puts "The current_company has been assigned"
puts params.inspect
end
def current_company
#company = Company.find_by_id(session[:current_company])
puts "The current_company helper has been called"
puts #company
puts params.inspect
end
Am I doing something wrong?
Edit 2
I have no idea why this isn't working. After the above edits, it appears as though the session[:company_id] is not being assigned so the current_company helper method is returning nil. I've tried printing the session paramaters puts session.inspect and can't find any company_id information. Anyone any idea why it isn't assigning the value?
Edit 3
Can't for the life of me figure out what's going wrong. I've tried multiple things including moving the current_company = #company into the set_company method in companies_controller.rb which now looks like this:
def company_home
puts "Test the current company"
puts "#{#company.id} #{#company.name}"
puts params.inspect
end
private
def set_company
#company = Company.find_by_id(params[:id])
if #company.nil?||current_user.organisation_id != #company.organisation.id
flash[:alert] = "Stop poking around you nosey parker"
redirect_to root_path
else
current_company = #company
end
end
The company_home method is being given a company object (I can see this in the console output below) but the current_company assignment is just not happening. Here's the console output for reference
Started GET "/company_home/1" for 80.55.210.105 at 2014-12-19 10:26:49 +0000
Processing by CompaniesController#company_home as HTML
Parameters: {"authenticity_token"=>"gfdhjfgjhoFFHGHGFHJGhjkdgkhjgdjhHGLKJGJHpDQs6yNjONwSyTrdgjhgdjgjf=", "id"=>"1"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
Company Load (0.3ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = 1 LIMIT 1
Organisation Load (0.3ms) SELECT "organisations".* FROM "organisations" WHERE "organisations"."id" = $1 LIMIT 1 [["id", 6]]
Test the current company
1 Cine
{"_method"=>"get", "authenticity_token"=>"gfdhjfgjhoFFHGHGFHJGhjkdgkhjgdjhHGLKJGJHpDQs6yNjONwSyTrdgjhgdjgjf=", "controller"=>"companies", "action"=>"company_home", "id"=>"1"}
Rendered companies/company_home.html.erb within layouts/application (0.1ms)
Company Load (0.6ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" IS NULL LIMIT 1
The current_company helper has been called
{"_method"=>"get", "authenticity_token"=>"gfdhjfgjhoFFHGHGFHJGhjkdgkhjgdjhHGLKJGJHpDQs6yNjONwSyTrdgjhgdjgjf=", "controller"=>"companies", "action"=>"company_home", "id"=>"1"}
CACHE (0.0ms) SELECT "organisations".* FROM "organisations" WHERE "organisations"."id" = $1 LIMIT 1 [["id", 6]]
Completed 200 OK in 280ms (Views: 274.0ms | ActiveRecord: 1.7ms)
As per above, under the line The current_company helper has been called, there's a blank line where puts #company should be outputting something. This means the current_company method is returning nothing.
Also, in the company_home method in the companies_controller, if I change puts "#{#company.id} #{#company.name}" to puts "#{current_company.id} #{current_company.name}" an error gets thrown.
Has anyone any idea why the def current_company=(company) isn't assigning a session parameter? Thanks
Final Edit
I've no idea why, but it appears the problem related to this:
def current_company=(company)
session[:current_company] = company.id
puts "The current_company has been assigned"
puts params.inspect
end
It looks as though this never gets called. I don't understand why as I've never used something like this before.
I'll put my fix in an answer.
Ok, you need to do two things.
First thing, you need to assign your company.id to a session variable
def current_company=(company)
session[:company_id]=company.id
end
Second, your helper method for current_company will be as follows:
def current_company
Company.find_by_id(session[:company_id])
end
This can be nil if there is no session[:company_id] or if it corresponds to no company. That's ok...
Next, it is quite unlikely to get it working without an id, if you use /companies both for your index and your show actions.
Now, for your first task. Setting the variable:
controller:companies_controller.rb
def show
# assuming you have a before_action, something like set_company, no need to redo it
current_company=#company # this will set the session variable
end
If you want your navbar to lead to your current_company, you will need to write:
<% unless current_company.nil? %>
<li><%= link_to "Company", current_company, :method => :get %></li>
<% end %>
I don't know what you want to do if there is no current company, so I just leave it out.
Instead of using a helper method to assign a value or search for a company, I've assigned a session variable in the set_company method in companies_controller.rb. This can then be accessed around the application
companies_controller.rb
private
def set_company
#company = Company.find_by_id(params[:id])
if #company.nil?||current_user.organisation_id != #company.organisation.id
redirect_to root_path
else
session[:current_company] = #company
current_company = #company
end
end
Related
I want to allow users to accept invitations, the accept tag is in the invite model itself (so I need to update the table). So far nothing occurs when the user clicks the accept button
View
<% #invites.where(user_id: current_user.id).find_each do |invite| %>
...
<%= button_to "Accept", accept_invite_invites_path(invite), method: :put %>
end
Routes
resources :invites do
collection do
get 'accept_invite'
end
end
Controller
def accept_invite
#invite = Invite.find(params[:id])
#invite.accept
end
def decline_invite
#invite = Invite.find(params[:id])
#invite.decline
end
def set_invites
#invite = #story.invites.find(params[:id])
end
def new
#invite = #story.invites.new
end
I get "undefined method `invites' for nil:NilClass" if I keep :update as a part of set_invites, removing update allows my code to run, but no changes to the database is made.
Model
def accept
accept = true
save
end
def decline
accept = false
save
end
Console
Processing by InvitesController#update as
Parameters: {"authenticity_token"=>"BDle9fqXHT9ZFctMbO4RvxfPuTQXe2Nq+b6/T29B3xjpYdtMozVUFLiRlaQFtuYzMrBceTQn8OtfGjJTe4wa/Q==", "id"=>"accept_invite"}
User Load (1.7ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 ORDER BY `users`.`id` ASC LIMIT 1
No template found for InvitesController#update, rendering head :no_content
Completed 204 No Content in 85ms (ActiveRecord: 1.7ms)
It's weird because the database is selecting from the user table rather than updating the invites table
So what is the problem? Is the route faulty? My set_invites method?
So what is the problem? Is the route faulty? My set_invites method?
Yes,your route is faulty. As I can see you declared your route on a collection, but you need it on a member. And also you should change it to put.
resources :invites do
member do
put 'accept_invite'
end
end
I'm running into a perplexing issue that I can only resolve partway, and hopefully, someone more experienced can tell me whether I can achieve what I wish, or if I'm barking up the wrong tree.
I have a Rails 4 application which uses Devise and CanCan. I'd like to make a small subset of application functionality available to guest users (not logged in). I can achieve this by specifying a get route to a controller method and using link_to to reach that method. I cannot, however, figure out how to get the value of a select box to pass along as parameters on that page without making that view a form using form_tag (there is no model associated with this view).
I can pass hardcoded params along like so:
<%= link_to "Month", activities_search_month_path(:resource_id => 4) %>
but I'd rather have something like:
<%= link_to "Month", activities_search_month_path(:foo => :resource_id) %>
where the second symbol refers to the value of a select_tag. This second example delivers a literal value of "resource_id" when I dump the :foo key unless I convert my view to a form.
If I turn the view into a form by enclosing all the erb in a form_tag, I get a 401 Forbidden error, after which the Devise sign in form is rendered. My guess is that any time you want to process a form, Rails (or Devise) demands authentication on some level. The behavior is the same when I use button_to rather than link_to, since button_to wraps itself in a form under the covers.
How can I set that resource_id argument in my link_to, or will I be forced to create a guest user access level and silently log in guest users? It's important for the UX that users can access this functionality with the least amount of effort possible.
Thanks in advance.
Addendum: quick_search method from controller
def quick_search
puts "quick search 0"
if(params[:time_period] == 'today')
#resource = Resource.find(params[:resource_id])
#site = Site.find(params[:site_id])
#time_period_string = "Activities for #{localize_date(Date.today)} at #{#resource.name}, #{#site.name}"
puts "quick search 1"
if user_signed_in?
puts "quick search 2a"
#activities = Activity.where("system_id = ? and start_date = ? and activity_status_id = ? and resource_id = ?", current_system_id, #today, 2, params[:resource_id])
else
puts "quick search 2b"
if(Setting["#{current_subdomain_not_signed_in}.quick_search_guest_access"] == 'true')
puts "quick search 3a"
current_system_id = current_system_id_not_signed_in
#activities = Activity.where("system_id = ? and start_date = ? and activity_status_id = ? and resource_id = ?", current_system_id, #today, 2, params[:resource_id])
else
puts "quick search 3b"
redirect_to '/users/sign_in'
end
end
end
Note: the quick_search method is never entered. CanCan (or maybe Devise) steps in immediately and redirects to sign in:
Console output:
Started GET "/activities/quick_search" for 127.0.0.1 at 2015-04-12 18:01:58 -0700
Processing by ActivitiesController#quick_search as HTML
(0.2ms) SELECT DISTINCT "systems"."subdomain" FROM "systems"
Completed 401 Unauthorized in 1ms
Started GET "/users/sign_in" for 127.0.0.1 at 2015-04-12 18:01:58 -0700
Processing by Devise::SessionsController#new as HTML
(0.2ms) SELECT DISTINCT "systems"."subdomain" FROM "systems"
Rendered layouts/_header.html.erb (0.8ms)
Rendered devise/shared/_links.html.erb (4.1ms)
Rendered devise/sessions/new.html.erb within layouts/application (14.7ms)
Rendered layouts/_footer.html.erb (0.0ms)
Completed 200 OK in 285ms (Views: 282.3ms | ActiveRecord: 0.2ms)
Ability.rb
can :quick_search, Activity
can :search_day, Activity
can :search_week, Activity
can :search_month, Activity
The odd thing is that link_to quick_search fails with a 401, but link_to the other three methods works fine -- I just can't get parameters to them dynamically.
If you are using CanCan(Can?) you can define a special ability for guests.
How does your Ability-model look?
Which controller are handling the action that you want to view?
How do you authenticate with CanCan in this controller?
https://github.com/CanCanCommunity/cancancan/wiki/CanCan-2.0
Under the "Defining Abilities" you can see a non-user example.
Fixing CanCan is probably the best option, if you do not want to:
For the part with the link and select box it would be easiest to handle as a form and then handle the redirect in the controller, it could also be done with a remote ajax form.
http://edgeguides.rubyonrails.org/working_with_javascript_in_rails.html
This should work:
<% form_tag Activity, activity_quick_search_path, remote: true do %>
<%= select_tag :resource_id...%>
<%= submit_tag %>
<%end%>
Edit after comments:
The culprit here is(was) an:
before_action :authenticate_user!
Causing Devise to redirect to sign in page.
However, if you have CanCan you shouldn't need the authenticate_user.
Short example:
With only Devise I would do:
class NewsController < ApplicationController
before_action :authenticate_user!
before_action :set_news, except: [ :index, :new ]
def index
#news = News.all
end
def show
end
def new
#news = News.new
end
def edit
end
def create
#news = News.new(news_params)
flash[:notice] = 'News created' if #news.save!
redirect_to #news
end
def update
#news.update! news_params
redirect_to #news
end
def destroy
#news.destroy!
redirect_to News
end
private
def news_params
params.require(:news).permit(some_attributes)
end
def set_news
#news = News.find(params[:id])
end
end
How it looks with CanCanCan:
class NewsController < ApplicationController
load_and_authorize_resource
def index
end
def show
end
def new
end
def edit
end
def create
flash[:notice] = 'News created' if #news.save!
redirect_to #news
end
def update
#news.update! news_params
redirect_to #news
end
def destroy
#news.destroy!
redirect_to News
end
private
def news_params
params.require(:news).permit(some_attributes)
end
end
Which I find super neat 😄
Hope that this can help as well.
I have a ConversationsController with two identical actions:
def index
#conversations ||= current_user.mailbox.inbox.all
#trash ||= current_user.mailbox.trash.all
end
def trashbin
#conversations ||= current_user.mailbox.inbox.all
#trash ||= current_user.mailbox.trash.all
end
The only difference is the information that is displayed in the views. The index page displays the data just fine. However, I get the following error for every instance variable that appears in the trashbin view:
undefined method 'count' for nil:NilClass
View:
<%= #conversations.count %>
Count just so happens to be the first method used in the view page. The views between the two actions are almost identical except for some plain text. I'm stumped as to why I'm getting an error on one action. I'm using <%= #conversations.count %> on the index page and it works fine.
The only possibility is an error with the routes file. I had some trouble getting the custom trashbin route to work so I'm thinking the issue is with the routes file:
resources :conversations do
member do
post :reply
post :trash
post :untrash
end
collection do
get :trashbin, :action => 'trashbin'
end
end
Any ideas?
Thanks!!
EDIT: Updated routes code (all of it has been posted).
Also, here is the error generated in the terminal:
Started GET "/conversations/trashbin" for 127.0.0.1 at 2014-01-02 17:38:34 -0500
Processing by ConversationsController#trashbin as HTML
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Rendered conversations/trashbin.html.erb within layouts/application (4.0ms)
This turned out to be a rookie mistake on my part. The "identical" action in the controller wasn't exactly identical. The controller action that wasn't working properly was listed under Private, which explains why the instance variable wasn't accessible to the view -_-.
Sorry for wasting your time everyone!
Ok ill be honest, i haven't spent much time looking for a solution yet seeing as how my son is keeping my attention running around. Either way I would like to ask a question for something that seems pretty simple but has stumped me thus far.
So to keep it simple lets say I have Users(w/model) and Home controllers, Home is the root route.
In the root directory I want to be able to see all posts made by the User using ajax to update a partial on the home page with the list of posts.
In the users controller I have a def called userposts with this in it
def userposts
#user = User.find_by_id(params[:id])
#userposts = #user.posts.all(:order => "created_at DESC")
respond_to do |format|
format.js { #userposts}
end
end
And in my view I have
<p id="aboutuser">
<% if #user.about? %>
<%= " " + #user.id.to_s %>
<% else %>
User has not yet filled this out.
<% end %>
</p>
<h3 id="authpostlink">
<%= link_to "List of all posts", user_userposts_path(#user.id), :id => #user.id, :remote => true %>
</h3>
my errors are as follows
Started GET "/users/2/userposts" for 127.0.0.1 at Sun Jan 15 13:36:23
-0600 2012 Processing by UsersController#userposts as JS Parameters: {"user_id"=>"2"} User Load (0.1ms) SELECT "users".*
FROM "users" WHERE "users"."id" IS NULL LIMIT 1 Completed 500 Internal
Server Error in 1ms
NoMethodError (undefined method posts' for nil:NilClass):
app/controllers/users_controller.rb:27:inuserposts'
Rendered
/home/n0de/.rvm/gems/ree-1.8.7-2011.03/gems/actionpack-3.1.0/lib/action_dispatch/middleware/templates/rescues/_trace.erb
(0.8ms) Rendered
/home/n0de/.rvm/gems/ree-1.8.7-2011.03/gems/actionpack-3.1.0/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
(0.8ms) Rendered
/home/n0de/.rvm/gems/ree-1.8.7-2011.03/gems/actionpack-3.1.0/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (3.2ms)
I do realize i did not post the _show.js.erb file that calls the action to update the div but according to the error messages it doesn't seem the process has gotten that far.
Assuming you have the following:
# /app/models/user.rb
class User < ActiveRecord::Base
has_many :posts
end
# /app/models/post.rb
class Post < ActiveRecord::Base
belongs_to :user
end
I would add a nested resource in your routes file:
#/config/routes.rb
resources :users do
resources: posts
end
You get a bunch of great "_path" methods for free (run $ rake routes from your console to see them all), and it gives you access to URLs such as /users/123/posts. This request will go to the index method of your PostsController and will automatically include :user_id => 123 in the params hash. You can then do the following:
# In your view:
<%= link_to "List of all posts", user_posts_path(#user), :remote => true %>
<div id="posts"></div>
# /app/controllers/posts_controller.rb
class PostsController < ApplicationController
respond_to :js # allows for AJAX requests
def index
if params[:user_id].present? # Do this if using the nested resource
#user = User.find(params[:user_id])
#posts = #user.posts.order('posts.created_at DESC')
else # Otherwise, treat it like a normal request
#posts = Post.all
end
respond_with #posts
end
end
Because the your request is sent remotely, you need a corresponding "js" version of your index view (note the file name below and see this Railscast for more explanation):
# /app/views/posts/index.js.erb
$('#posts').html("<%= escape_javascript(render(#posts)) %>");
This will render out the posts into that <div id="posts"> tag. (You'll probably need a "_post.html.erb" partial in /app/views/posts/" as well.)
However, having said all this, are you sure you need to do this via AJAX? You could simply preload all the posts in the UsersController#show method, initially hide the list using CSS, and then add a jQuery toggle() method on that link. Anyway, hope this makes sense and is helpful.
I'm trying to use an after_commit method to pass parameters from the post to the user model where it is shared to twitter with another method.
It works fine when I just pass it something from the post model like 'title' or 'content':
post.rb
after_commit :share_all
def share_all
if user.authentications.where(:provider => 'twitter').any?
user.twitter_share(title, content)
end
end
user.rb
def twitter_share(title, content)
twitter.update("#{title}, #{content}")
end
But this is as far as my understanding goes, I've read elsewhere that I could pass 'self' instead of 'title' and 'content', and still be able to use 'title' and 'content' plus any thing else from the model such as 'created_at'. However, I can't seem to get this working, I've tried this :
post.rb
def share_all
if user.authentications.where(:provider => 'twitter').any?
user.twitter_share(self)
end
end
user.rb
def twitter_share(self)
twitter.update("#{title}, #{content}")
end
And I get SyntaxError (/Users/ihal/Desktop/dayor/app/models/user.rb:118: syntax error, unexpected keyword_self, expecting ')'
def twitter_share(self)
And it posts this to twitter #< Post:0x00000101d6e1e0>
My question is how to you properly setup passing 'self' so that any parameter could be called with twitter.update()?
Also how do you go about pulling out URL for the post, so that you could pass the URL to share on twitter?
Edit:
trying Rails.application.routes.url_helpers.post_url(#post, :host => 'myhost.com')
in post.rb
class Post < ActiveRecord::Base # line 19
after_commit :share_all
Rails.application.routes.url_helpers.post_url(#post, :host => 'myhost.com') #line 37
def share_all
if user.authentications.where(:provider => 'twitter').any?
user.twitter_share(self)
end
end
When I go to delete a post, I get the error :
Started POST "/posts/32" for 127.0.0.1 at 2011-04-15 14:57:17 -0700
Processing by PostsController#destroy as HTML
Parameters: {"authenticity_token"=>"x8KkqLLCLdTOouUfCMzyWWmwxLIKThnE1n3rQNSkew8=", "id"=>"32"}
User Load (1.1ms) SELECT "users".* FROM "users" WHERE ("users"."id" = 5) LIMIT 1
Completed in 82ms
ActionController::RoutingError (No route matches {:action=>"destroy", :controller=>"posts"}):
app/models/post.rb:37:in <class:Post>'
app/models/post.rb:19:in'
app/controllers/posts_controller.rb:36:in `authorized_user'
Rendered /Users/ihal/.rvm/gems/ruby-1.9.2-p136#rails3gemset/gems/actionpack-3.0.1/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (1.2ms)
post controller
def destroy
#post.destroy
redirect_to root_path
end
private
def authorized_user
#post = Post.find(params[:id]) #line 36
redirect_to root_path unless current_user?(#spost.user)
end
end
Have you thought about using an Observer? That way you can keep the after_commit stuff, which does not appear to to belong in the model, in its proper place. Plus it helps simplify your model instead of cluttering it up.
For the syntax error, self is a reserved word. Rename the variable name in the method declaration. So try something like:
def twitter_share(post)
# do stuff
end
To access the url helpers outside of the controller, use:
Rails.application.routes.url_helpers.post_url(#post, :host => 'myhost.com')
Don't forget to use the :host option when accessing the url helpers outside the controller so the helper has context.
what I can see in code is, title and content are attributes of Post. so passing them as theire name will be converted into their value.
now you are passing self in twitter_share from post then the post object will be passed to that method. you need to modified the twitter_share method as below to make it work
def twitter_share(post)
twitter.update("#{post.title}, #{post.content}")
end
def twitter_share(post)
twitter.update("#{post.title}, #{post.content}")
end
You are passing a Post into the user.twitter_share method.