Rails help integrating nested? user specific uploads into an existing app - ruby-on-rails

My app allows users to log in and have sessions. I have a user controller and a sessions controller, mainly developed from railscasts "authorization from scratch".
I recently added the ability to upload files to S3 using a jquery uploader... Again lots of this comes from railscasts "uploading to amazon S3".
The problem is my uploads are not user specific. Right now my "upload" controller has an "authorize" before_filter to ensure you must be logged in to access the uploader; however once a user uploads a file, ALL users see the upload! Not good! I need to ensure users only see the respective files they upload.
I've tried a few things but none seem to work. I'm looking for some direction on how to ensure users only see the files they upload. I'm following different railscasts and rails documentation on nesting resources (I think that is how I have to do this?) but I keep missing something as there seems to be lots of changes that I don't 100% understand. I fix one error, then hit another, and am wondering if I'm even going down the right path or maybe I'm missing something?
The way I thought this should work is to first nest the resource:
resources :users do
resources :cust_uploads
end
Then I modified the models as below and ran "rake db:migrate" to tie them together... I may need to manually modify a migration file with a foreign id field?:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email, :password, :password_confirmation
validates_uniqueness_of :email
has_many :CustUploads
end
class CustUpload < ActiveRecord::Base
attr_accessible :cust_file_url, :name
before_create :default_name
belongs_to :User
def default_name
self.name ||= File.basename(cust_file_url, '.*').titleize if cust_file_url
end
end
This gives me tons of path errors which I'm fighting through now... as my new_cust_upload_path is probably something like new_user_cust_upload_path
I also think my forms and controllers need lots of modification....
I'm using form_for
<%= form_for(#cust_upload) do |f| %>
Which I think should now be #user.cust_upload?
controllers at the moment:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
session[:user_id] = #user.id
redirect_to root_url, notice: "Thank you for signing up!"
else
render "new"
end
end
end
class CustUploadsController < ApplicationController
before_filter :authorize
def index
#cust_uploads = CustUpload.all
end
def show
#cust_upload = CustUpload.find(params[:id])
end
def new
#cust_upload = CustUpload.new
end
def create
#cust_upload = CustUpload.create(params[:cust_upload])
end
def edit
#cust_upload = CustUpload.find(params[:id])
end
def update
#cust_upload = CustUpload.find(params[:id])
if #cust_upload.update_attributes(params[:cust_upload])
redirect_to #cust_upload_url, notice: "Cust upload was successfully updated."
else
render :edit
end
end
def destroy
#cust_upload = CustUpload.find(params[:id])
#cust_upload.destroy
redirect_to cust_uploads_url, notice: "Cust Upload was successfully destroyed"
end
end
Any direction will be greatly appreciated. I've been through many tutorials and can make simple things work from scratch, I just can't seem to integrate this functionality with my existing app. There is something here I can't wrap my brain around and I'm hoping someone can provide me with that Eurika moment! Thanks
EDIT
routes.rb and my models appear to have the appropriate connections (code below). When in terminal I type "rake routes" I get a list as expected (see below) however I get and error: "No route matches {:action=>"show", :controller=>"cust_uploads"}" for a link with user_cust_uploads_path. There is a show template in the cust_uploads path and rake routes says it exists! What am I missing?
user_cust_uploads GET /users/:user_id/cust_uploads(.:format) cust_uploads#index
POST /users/:user_id/cust_uploads(.:format) cust_uploads#create
new_user_cust_upload GET /users/:user_id/cust_uploads/new(.:format) cust_uploads#new
edit_user_cust_upload GET /users/:user_id/cust_uploads/:id/edit(.:format) cust_uploads#edit
user_cust_upload GET /users/:user_id/cust_uploads/:id(.:format) cust_uploads#show

Considering you want to achieve
Users who upload stuff should only see them.
Why don't you associate the uploads with a specific user id and then when showing them in the view pull them from their own id (current_user.uploads)

Related

How to write Rspec for basic Show, Create, Destroy, Edit methods in rails (with or without FactoryGirl)

I have a basic user_controller.rb file like this:
class UserController < ApplicationController
def new
#user = User.new
end
def index
#user = User.all
end
def show
#user = User.find(params[:id])
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to #user
else
render 'edit'
end
end
def destroy
#user = User.find(params[:id])
#user.destroy
redirect_to action: 'index'
end
private
def user_params
params.require(:user).permit(:name, :key, :desc)
end
end
This is my (model) user.rb file:
class User < ApplicationRecord
validates :name, presence: true
validates :key, uniqueness: true, presence: true
validates :desc, presence: true
end
And created a factories.rb file (in the specs folder):
FactoryGirl.define do
factory :user do
name "TestUser"
key "TKey"
desc "TestDescription"
end
end
I tried several ways to make the specs work but I can't because of the confusing syntax.
The only one which worked was (for the 'C' in the CRUD operations, the below file is user_controller_specs.rb):
require 'rails_helper'
require 'factory_girl_rails'
RSpec.describe UserController, :type => :controller do
let(:temp) { FactoryGirl.build(:user) }
describe "POST create" do
it "should redirect back to the index page" do
post :create, :user => { :user => temp }
expect(get: user_url(subdomain: nil)).to route_to(controller: "user", action: "index")
end
end
end
I skimmed through several tutorials to find what should be the correct approach for CRUD operations but didn't got any simple to understand specs. I am trying to write these in the specs/controllers folder but these are giving errors. What should be the correct syntax to write the specs?
PS: I'am new to Ruby/Rails and trying to write test cases with Rspec and FactoryGirl. Any help is appreciated.
Edit:
Maybe I framed the question wrongly... I'm more interested in the syntax part. If I get to know an example how to write one, I'll be able to write others by changing some tiny bits of logic here and there.... Let's say I have a basic test case just to see whether updating a user details is not returning an error because of validations, how should I write it with (or without) Factory Girl gem?
It's a pretty broad question, but in any kind of test, you want test whatever use cases you have available to you. Example--are there different paths users might follow from hitting a specific controller action.
So you want your test to cover the basics. When you hit the create action, is a user actually created? If the relevant params are missing, is an error thrown? Use cases will drive your expectations.
With rspec controllers specifically, you'll use the appropriate verb and the name of the action, and pass it whatever parameters are necessary.
post :create, :user => { :user => temp }
That basically says, "do a post request to my create an action and pass it the parameters inside these curly braces."
After running that rspec gives you access to the response. You can always log the response after a controller request to help you debug the situation: p response.
You'll follow up each type of request with an expectation. The expectation should answer the question: "What did I expect hitting this action to do?" If you were, for instance, hitting the user update action and passed a param to change the user's age to 21, your expectation might be something like:
expect(user.age).to eq(21)
A great resource is the rspec documentation on relish. https://relishapp.com/rspec
"How to" do a broad general thing is a tough question to answer like this. My advice would be to try to actually test one, log the failure case, and post those logs in a new question and people on SO can help you work through testing a particular action you're struggling with.

How to check if duplicate file exists with paperclip but only in current board

I currently have paperclip saving the fingerprint with :fingerprint => :post_file_fingerprint and my routes are set up like this:
resources :boards, :path => '' do
resources :posts, :path => 'thread' do
resources :replies
How can I check if post_file_fingerprint exists against the newly generated fingerprint, and if it does don't create the post.
I currently I have in my post.rb:
before_save :check_exists
def check_exists
if Post.exists?(:post_file_fingerprint [:fingerprint.to_s])
flash.now[:error] = "Duplicate"
render #board
end
end
However this code still allows posts to be saved, and has no checking based on board.
I assume you trying to rise a flash in a model. There is no way to do that because flash is a method defined in ActionController::Base.
If you'd like to validate your Post model you should implement custom validation.
before_save :check_exists
def check_exists
errors.add(:post_file_fingerprint, "Duplicate") if Post.exists?(post_file_fingerprint: [:fingerprint.to_s])
end
I'm not sure your checking condition works properly because you provided too few details about it. Please be sure that works.
And in your controller
def create
# some code
if #post.save
redirect_to #post
else
flash[:error] = #post.error.messages
render 'new'
end
end
Works as follows:
Before saving a model AR callback will be initiated, and if Post isn't valid — model instance will be set to invalid with message you provided.
AR will not save invalid model into the DB, and then you'll set flash[:error] with custom error message.
More details here.
Hope that helps.
UPDATE
I tested code and made some improvements. The following example works:
def check_exists
errors.add(:post_file, "Duplicate") if Post.exists?(post_file_fingerprint: attributes['post_file_fingerprint'])
end

Rails 4 - Couldn't find User without an ID

I'm new to rails, so any explanation & advise would much appreciated.
i have a webpage in which i would like any user to view that page not just the current_user, but i am unsure how to correctly define the instance variable #user in my controller
in my static_pages_controller.rb i have the below action recruiterpg
static_pages_controller.rb
def recruiterpg
#user = User.find(params[:user_id])
#adverts = #user.adverts
#applications = #user.forms
end
in my controller, i have defined user as #user = User.find(params[:user_id]) but this breaks my code in the views; views/static_pages/recruiterpg.html.erb
when i define user as #user = current_user my code in the views works perfectly fine
what am trying to do is: for my views, the recruiterpg.html.erb, i would like
any user to be able to view the page not only the current_user to
view the page. Could one kindly advise me and explain to me how to
define #user correctly in my status_pages_controller.rb. i also
tried #user = User.find(params[:id]) but my code still breaks in the
views - i get the error message
Couldn't find User without an ID
You need to make sure you are passing a user_id to the recruiterpg action. For example, try this url in your browser (set user_id to a known id in the users table):
http://localhost:3000/dashboard?user_id=1
A suggested modification to your action:
def recruiterpg
#user = User.find params.require(:user_id)
#adverts = #user.adverts
#applications = #user.forms
end
If params[:user_id] isn't defined, you want to find a way to make visible what is being defined.
If you throw the following statements into your controller...
def recruiterpg
...
puts params
...
end
...you should see something like the following get spit out in your console when you load the page...
{"controller"=>"static_pages", "action"=>"recruiterpg", "id"=>"49"}
Take a look at the Rails guide for parameters. They can get defined in one of three ways.
One: As a query string similar to Sean's answer above.
Two: Routing parameters. See section 4.3 in the Rails guide. In your case, that would mean you should have something like the following in routes.rb:
get '/dashboard/:user_id' => 'staticpages#recruiterpg'
Note that there's nothing magic about :user_id in that string.
Three: From a form which it doesn't seem like applies here, since a user isn't submitting data.
Since you're new, here is some information for you:
User Story
Firstly, the best way to resolve errors is to identify your user story.
A "user story" is a software principle in which you put the "user's perspective" first -- explaining how the software should work in conditions defined from how the user engages with it.
One of the main issues you have with your question is your user story is very weak; it's hard to decifer what you're trying to achieve.
Next time you ask a question, you should try your hardest to describe how the user should see your app, before providing code snippets :)
Controller
Your main issue is an antipattern.
An antipattern is basically a "hack" which will likely break another part of your app in future. Kind of like duct tape for software):
#app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def recruiterpg
#user = User.find(params[:user_id])
#adverts = #user.adverts
#applications = #user.forms
end
end
So you're showing a static page but yet you want to populate it with data?
Hmm...
What you should be doing is something like the following:
#config/routes.rb
resources :users do
resources :recruiters, only: :index #-> url.com/users/:user_id/recruiters
end
#app/controllers/recruiters_controller.rb
class RecruitersController < ApplicationController
def index
#user = User.find params[:user_id]
#adverts = #user.adverts
#applications = #user.forms
end
end
This will allow you to populate the following view:
#app/views/recruiters/index.html.erb
<%= #adverts %>
--
It's important to note the structure of the controller / routes here.
The issue you have is that you're calling a "static page" and expecting to have params available to find a User. This can only happen if you have params available...
Params
Rails is not magic, and as such if you want to look up a user, you have to provide the parameters to do so.
This is why you're able to look up current_user -- the params are already set for this user.
As such, you'll need to use something called nested routes in order to attain a user ID other than that of current_user:
#config/routes.rb
resources :users do
resources :recruiters #-> url.com/users/:user_id/recruiters
end

Rails has_one build_association deletes record before save

So this has been asked previously, but with no satisfying answers.
Consider two models, User, and Subscription associated as such:
class User < ActiveRecord::Base
has_one :subscription, dependent: :destroy
end
class Subscription < ActiveRecord::Base
belongs_to :user
end
Inside of SubscriptionsController, I have a new action that looks like this
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
Given that a subscription already exists for a user record, I'm faced with the following problem:
user.build_subscription is destructive, meaning that simply visiting the new action actually destroys the association, thereby losing the current subscription record.
Now, I could simply check for the subscription's existence and redirect like this:
def new
user = User.find(params[:user_id])
if user.subscription.present?
redirect_to root_path
else
#subscription = user.build_subscription
end
end
But that doesn't seem all that elegant.
Here's my question
Shouldn't just building a tentative record for an association not be destructive?
Doesn't that violate RESTful routing, since new is accessed with a GET request, which should not modify the record?
Or perhaps I'm doing something wrong. Should I be building the record differently? Maybe via Subscription.new(user_id: user.id)? Doesn't seem to make much sense.
Would much appreciate an explanation as to why this is implemented this way and how you'd go about dealing with this.
Thanks!
It depends on what you want to do
Thoughts
From what you've posted, it seems the RESTful structure is still valid for you. You're calling the new action on the subscriptions controller, which, by definition, means you're making a new subscription (not loading a current subscription)?
You have to remember that Rails is basically just a group of Ruby classes, with instance methods. This means that you don't need to keep entirely to the RESTful structure if it doesn't suit
I think your issue is how you're handling the request / action:
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
#subscription is building a new ActiveRecord object, but doesn't need to be that way. You presumably want to change the subscription (if they have one), or create an association if they don't
Logic
Perhaps you could include some logic in an instance method:
#app/models/user.rb
Class User < ActiveRecord::Base
def build
if subscription
subscription
else
build_subscription
end
end
end
#app/controllers/subscriptions_controller.rb
def new
user = User.find(params[:user_id])
#subscription = user.build
end
This will give you a populated ActiveRecord, either with data from the subscription, or the new ActiveRecord object.
View
In the view, you can then use a select box like this:
#app/views/subscriptions/new.html.erb
<%= form_for #subscription do |f| %>
<%= "User #{params[:user_id]}'s subscription: %>
<%= f.collection_select :subscription_id, Subscription.all,:id , :name %>
<% end %>
They are my thoughts, but I think you want to do something else with your code. If you give me some comments on this answer, we can fix it accordingly!
I also always thought, that a user.build_foobar would only be written to the db, if afterwards a user.save is called. One question: After calling user.build_subscription, is the old subscription still in the database?
What is the output user.persisted? and user.subscription.persisted?, after calling user.build_subscription?
Your method to check if a subscription is present, is IMHO absolutely ok and valid.
I came across this today and agree that deleting something from the db when you call build is a very unexpected outcome (caused us to have bad data). As you suggested, you can work around if very easily by simply doing Subscription.new(user: user). I personally don't think that is much less readable then user.build_subscription.
As of 2018 Richard Peck's solution worked for me:
#app/models/user.rb
Class User < ActiveRecord::Base
def build_a_subscription
if subscription
subscription
else
build_subscription
end
end
end
My issue was that a user controller didn't have a new method, because users came from an api or from a seed file.
So mine looked like:
#app/controllers/subscriptions_controller.rb
def update
#user = User.find(params[:id])
#user.build_a_subscription
if #user.update_attributes(user_params)
redirect_to edit_user_path(#user), notice: 'User was successfully updated.'
else
render :edit
end
end
And I was finally able to have the correct singular version of subscriptions in my fields_for, so :subscription verses :subscriptions
#app/views
<%= f.fields_for :subscription do |sub| %>
<%= render 'subscription', f: sub %>
<% end %>
Before I could only get the fields_for to show in the view if I made subscriptions plural. And then it wouldn't save.
But now, everything works.

How do I log create actions?

I am trying to create a logging feature on my RoR application that logs all the actions performed by the user on a given controller in a model. I tried implementing it with filters but they didn't solve my problem because they didn't allow me to properly log "create" actions.
"Create" actions are tricky because, when the before/after filters are called the action hasn't been saved yet, therefore, I don't have the corresponding id of the model (which I need).
I know I could use "after_commit", but this would greatly increase the complexity of the logging feature, since the "parameters" saved in each log entry are exposed to the controller, but not to the model.
Is there a way to dynamically add an "after_commit" filter to an instance of ActiveRecord?
Read this, I think this is nice solution: Notifications
This is how i log, i have a users controller create action like this:
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "Welcome, #{#user.username}"
redirect_to(:controller => "users", :action => "home")
session[:id] = #user.id
else
render("home")
end
end
Now i would like to log that a user was created, then i do this:
First create an AuditLogger class in User.rb(model):
class User < ActiveRecord::Base
...some stuff other....
class AuditLogger < Logger
def format_message(severity, timestamp, progname, msg)
"#{timestamp.to_formatted_s(:db)} #{severity} #{msg}\n"
end
end
Then back to the controller(users.rb)
def create
#user = User.new(params[:user])
if #user.save
logfile = File.open("#{Rails.root}/log/my_log.log", 'a')
audit_log = AuditLogger.new(logfile)
audit_log.info "#{#user.firstname} was created successfully"
redirect_to(:controller => "users", :action => "home")
else
render("home")
end
end
Also you will need to create a file in your log directory called my_log.log. Hopefully it should be able to log. I know its not the best solution and there i are better ways of doing it, but at the time i needed something to work urgently, so i stuck with it.
Checkout these links:
rails logging tips
alternative logging solution

Resources