I am creating an application that lets users store data in their personal dropbox in a protected folder that the application controls. So each user needs to store and access files in their own personal dropbox account.
To do this I'd like to leverage the paperclip-dropbox gem for storage. It allows paperclip to directly upload to dropbox: https://github.com/janko-m/paperclip-dropbox.
Here is the code that sets the authorization information for the paperclip-dropbox gem. NOTE: current_user does not work at the moment. I'm just putting that there to outline what would need to happen for the current setup to work.
Image.rb
has_attached_file :avatar,
:storage => :dropbox,
:dropbox_credentials => {app_key: DROPBOX_KEY,
app_secret: DROPBOX_SECRET,
access_token: current_user.token,
access_secret: current_user.secret,
user_id: current_user.uid,
access_type: "app_folder"}
Notice the dropbox authentication requires the current_user to get that particular set of credentials.
I know that the current_user is not supposed to be accessed from the model and I'd like to keep it that way so could anyone help me figure out how to do that with this current setup? Or suggest a better alternative?
Basically, I need to conditionally change the access_token, access_secret, & user_id on a per user basis.
Thanks!
I'm going to answer my own question because the other answers were too vague to accept - although they were on the right path. I think the community would prefer an answer with more code to back it up.
So here goes. To change the has_attached_file on a dynamic basis, you have to have a user_id column in the attachment model so that you're not calling current_user (which isn't possible without ugly hacks). Then you need a belongs_to as well to complete the user association. Let's assume I'm attaching an audio file to a Song model for this example.
The key to getting the dynamically set variables is to initialize the attachment with the after_initialize callback.
Song.rb
belongs_to :user
has_attached_file :audio
after_initialize :init_attachment
def init_attachment
self.class.has_attached_file :audio,
:storage => :dropbox,
:dropbox_credentials => {app_key: DROPBOX_KEY,
app_secret: DROPBOX_SECRET,
access_token: self.user.token,
access_token_secret: self.user.secret,
user_id: self.user.id
access_type: "app_folder"},
:dropbox_options => {}
end
You are, of course, free to setup your association differently, but this is a working code example for the question posed.
Just found this resource you might gain benefit from: Ruby on Rails - Paperclip and dynamic parameters
More specifically for you, I thought this might shed some light onto what you're doing:
# AssetsController
def create
#project = Project.find(params[:project_id])
#asset = #project.assets.build(params[:asset])
#asset.uploaded_by = current_user
respond_to do |format|
# all this is unrelated and can stay the same
end
end
Notice the "#asset.uploaded_by" is set in the controller? Maybe you could pass similar variables to your model? I don't know how I would do it specifically, but you'd basically be able to set the save options before you try and save the file, giving you the ability to dynamically set the options
I think first thing you should do is to set association Image.belongs_to :user - you could then use simply user.token etc. instead of referencing current_user.
Now the hard part. You can't simply type:
access_token: user.token
because self is Image class which simply doesn't respond to user method (it's instance method). My idea is to modify this gem so it could accept lambdas as arguments with attachment instance (for example) passed to this lambda on call. The problem is I don't know if it's hard to modify this gem that way yet.
Related
for now when i click user with id = 1, on url bar its
users/1
I want to change it to
users/[encrypt]
or
users/some_user
is there any way to do that on rails?
What about using a permalink instead of the users id? i.e. users/[permalink] and you can configure the permalink to anything you like as long as it is a unique value
Checkout the friendly_id gem: https://github.com/norman/friendly_id
Rails uses to_param method when displaying object in url.
If you change to_param method in user it will, be used to display data instead of id.
By default rails has implemented to_param to return id of the object.
For example
class User < ActiveRecord::Base
...
def to_param
"#{self.first_name}-#{self.last_name}" # or whatever you want to use
end
...
end
In your url you will have /users/first_name-last_name or whatever your to_param method returns. By default to_param returns id and in url you get /users/4. and in your controller you can find user with id 4, but when you change to_param method, you have to change respectively the way you fetch user from database.
Example:
I change my to_param method to return nick_name from database, and it is unique for the particular user, that I can use to find user from database.
In router
change the mappings for params
get 'users/:nick_name', 'users#show'
In controller
User.find_by :nick_name => params[:nick_name]
like others in this post says , i use
i use
https://github.com/norman/friendly_id this way:
# app/models/secret.rb; this would go in the model you want to obfuscate
class Secret < ActiveRecord::Base
has_friendly_id :code, :use_slug => true
validates :name, :uniqueness => true
def code
Digest::SHA1.hexdigest self.name
end
end
it’s simple. If your security needs are serious you’d probably want something a little more complex (not to mention more layered than a basic obfuscation technique), but I wanted to share an out-of-the-box way to use a gem that already exists (and may even be in use in your app already)
My class has a column secret which I generate using a specific process. Because of this, I don't want to let the user update this field via an edit form.
However, if the user adds manually the secret tag to the form and submit it, my object's secret gets updated too.
def update
object.attributes = params[:my_class]
end
I guess I create a before filter like this one
before_filter :clear_secret, :only => :update
def clear_secret
params.delete(:secret)
end
I would like to know if there is a better way to do this. Could I do this from the model? Is there a already existing Rails method for this?
If you're using Rails 3, you can protect this via attr_protected
class YourModel < ActiveRecord::Base
attr_protected :secret
end
A user attempting to inject a secret field into the form will instead cause Rails to raise a ActiveModel::MassAssignmentSecurity::Error.
However, this mechanism is deprecated in Rails 4 in favor of the strong_parameters mechanism referenced by #gylaz.
It's conventional to use strong_parameters gem to do this.
Using said gem you can permit only the attributes that can be updated, like this:
params.permit(:some_attr, :another_attr)
I'm modeling my User model and adding profile attributes.
I'm looking to add a set of social URLs for the user, starting with about 3 or 4 options and possibly growing to more than 15. It would be very easy for me to just create a separate attribute on the User model for every URL, and then run through them all separately for the profile page. Although this seems like it might be redundant, would this be inappropriate or should I go ahead since the attributes just store a simple string?
If I shouldn't load them all onto the User model:
What would be the best way to go about doing this with a separate model? Any how would I be able to grab all the social urls a user has created in an #social_urls array?
I'm also looking to associate a specific thumbnail image with each website URL to be displayed with the #social_urls.each do block. I'm confused on how I would call dynamically to the correct image.
Thanks so much for any help or insight.
Edit: would this work to attach a html class with the social site name? and then create a thumbnail using css?
# In erb or (better) a helper
<% User::SOCIAL_SYSTEMS.each do |social_system|
url = #user.send(social_system)
if url %>
<p><a href="<%= url %>" class="<%=User::SOCIAL_SYSTEM_NAMES[social_system]%>"><%=
User::SOCIAL_SYSTEM_NAMES[social_system] %></a></p>
<% end
end %>
Creating additional attributes on the User model for independent facts (the social urls) is fine.
Or you could have another two models just for social urls with structure
SocialUrl
id
user_id
social_system_id
value
SocialSystem
id
name # name of the social system ("Google +", "Facebook", etc)
But no need for the additional complexity of the two extra tables unless you really see a value.
Re:
running through them all (the social url attributes)
You can (and should) create helper methods for your User model. Eg, you can have the names of the social attributes as a CONST array and then use #send to retrieve all their values.
Added The tradeoffs between the two styles (either using the User model or creating two additional models)--
How often will you be adding new social systems?
In terms of space, db storage is so cheap that it makes no difference. -- Ie the User model style is sparser storage of the data.
Extra two models means two more controllers, two more sets of views, testing, security configuring so that only admin can add a new social system, etc.
I think I would focus on the main part of my app and just use the User model. Simpler and sweeter.
Added Re comments:
For the thumbnail, paperclip or similar is the way to go. -- It does create an additional model and table, but the plugin handles all the details.
For showing the urls, you'd use a helper or two. Eg
User model
facebook_url string
google_plus_url
# Constants defined in User model
SOCIAL_SYSTEMS = ['facebook_url', 'google_plus_url']
SOCIAL_SYSTEM_NAMES = {'facebook_url' => 'Facebook',
'google_plus_url' => 'Google +'}
# In erb or (better) a helper
<% User::SOCIAL_SYSTEMS.each do |social_system|
url = #user.send(social_system)
if url %>
<p><a href="<%= url %>"><%=
User::SOCIAL_SYSTEM_NAMES[social_system] %></a></p>
<% end
end %>
Added ps
If you need to store more than one fact per social system (eg url for the profile from the social system plus the person's latest score from the social system), then I would go for the additional tables. But if you only need a single fact (eg the person's profile url), then adding to the user model is fine. (imho)
you probably want to have one model to store the urls for each user, then another model to manage the thumbnails for each site. unless you want personal icons for each user, in which case you will probably include the has_attached_file on the User model rather than making a SocialSite model. paperclip is the prevailing attachment gem for rails, that's where the has_attached_file method comes from.
class User < ActiveRecord::Base
has_many :social_urls
end
class SocialUrl < ActiveRecord::Base
belongs_to :user
belongs_to :social_site
end
class SocialSite < ActiveRecord::Base
has_attached_file :thumbnail
has_many :social_urls
end
Do you really need an extra model for that?
if you have to store a growing number of URL for social networking sites or other web-sites, you could do the following:
1) define a migration to add a text column :social_sites to your User model
class AddSorialSitesToUsers < ActiveRecord::Migration
def self.up
add_column :users, :social_sites, :text
end
def self.down
remove_column :users, :social_sites
end
end
2) define the :social_sites attribute as to be serialized
class User < ActiveRecord::Base
serialize :social_sites
end
3) Then assign a Hash or an OrderedHash to user.social_sites and save the record..
u = User.find(1)
u.social_sites = {:facebook => 'http://facebook.com/somebody', :twitter => "http://twitter.com/somebody", ...}
because it's a Hash, you can assign as many different Service names as keys as you like.. and then later iterate over the Hash keys to retrieve them...or directly access them with the name of the Site
e.g.:
u = User.find(1)
puts u.social_sites[:facebook]
puts u.social_sites[:twitter]
...
Enhancement:
If you want to store more than just the URL for each site, you could use nested hashes as follows:
u.social_sites = {:facebook => {:url => "http://facebook.com/username", :icon => "url-or-filename-to-the-icon"} ,
:twitter => {:url => "http://twitter.com/username", :icon => "url-or-filename-to-the-icon"}
}
Rails will serialize the Hash(es) into the single attribute :social_sites, and when you access it, it will unserialize it -- you can just access it as any other Hash
u = User.find(1)
puts u.social_sites[:facebook][:url]
=> "http://facebook.com/username"
puts u.social_sites[:facebook][:icon]
=> "url-or-filename-to-the-icon"
hope this helps
I am trying to implement specific object (row) authorisation using cancan, I want it to work in a way that a user can only make a change(update/edit) to a Record if he/she has the role for that specific Record. after consulting the cancan docs I tried doing the following:
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Record do |record|
user.can_edit(record)
end
end
end
class User
has_many :assignments
has_many :roles_as_editor, :through => :assignments, :class_name => "Role", :source => :role, :conditions => {:edit => true}
def rec_as_editor
self.roles_as_editor.collect{ |x| Record.where(:cp_secondary_id => x.record_id) }.flatten.uniq
end
def can_edit(rec)
rec_as_editor.include?(rec)
end
end
The can_edit method takes in a Record object and ensures that a User has the role necessary to make a change to it by returning true or false. this method is tested and works correctly so the problem seems to be with the CanCan code because when i try editing a Record that the user dosent hold the role for it still allows me to make changes to the Record, anyone know why this wont work?
If you require any further information please let me know through a comment.
Thank You
Are you authorizing the resource in the controller?
you should have load_and_authorize_resource in your controller
or
def edit
#critical_process = CriticalProcess.find(params[:id])
#this here is what you use
authorize! :edit, #critical_process
end
in your edit method inside the critical process controller.
I personally prefer to keep this logic completely separate from models so that I don't have to dig into model code to find authorization issues. In other words, user.can_edit checks for authorization which is what the ability file is supposed to be in charge of. Shouldn't matter though... in this case I think you might have a problem inside the can_edit method. I have used code that looks nearly identical to yours without problems many times like this:
can :manage, Record do |record|
user.has_role?(:record_manager)
end
I suggest including your code for can_edit or use the debugger to see what value gets returned from can_edit.
I think the problem comes from the way you query for the records that are supposed to have the user as an editor.
I copy/pasted your code and built the other associations from scratch.
And testing it in the console it works as expected when I use it:
>> u = User.last
>> a = Ability.new(u)
>> a.can :edit, Role.last
false
The only thing I changed is the query for the records: it seemed to look for a record that owns the role (your Role has a record_id) but then looks for the same key under cp_secondary_id.
I think something is wrong in your query, but what depends on your schema and associations:
roles_as_editor.collect{ |x| Record.where(:cp_secondary_id => x.record_id) }.flatten.uniq
as I understood your code we are traversing associations like this:
User=>Assignment<=Role(boolean edit flag)<=Record
-EDIT-
After reading about the Delegate method from the first answer, my question is this, is it possible to delegate two different methods to another single method.
IE: I currently have: #photo.attachment.file.url, and #photo.attachment.height, and #photo.attachment.width
I'd like to be able to access all of these via #photo.file.url, #photo.file.height, #photo.file.width.
The reason for the syntax is Attachment is a model that uses Paperclip to manage files, Paperclip is generating the .file method (the model is called Attachment, the model uses Paperclip's has_attached_file :file).
-ORIGINAL QUESTION-
I was wondering about aliasing methods and attributes in Ruby (I think this is a general ruby question, although my application is in Rails 3):
I have two models: Photo has_one Attachment.
Attachment has "height" and "width" attributes, and a "file" method (from Paperclip).
So by default I can access bits of the Attachment model like so:
photo.attachment.width # returns width in px
photo.attachment.height # returns height in px
photo.attachment.file # returns file path
photo.attachment.file.url #returns url for the default style variant of the image
photo.attachment.file.url(:style) #returns the url for a given style variant of the image
Now, in my photo class I have created this method:
def file(*args)
attachment.file(*args)
end
So, now I can simply use:
photo.file # returns file path
photo.file.url # returns file url (or variant url if you pass a style symbol)
My question is, I was able to direct photo.attachment.file to just photo.file, but can I also map height and width to photo.file, so that, for the sake of consistency, I could access the height and width attributes through photo.file.height and photo.file.width?
Is such a thing possible, and if so what does it look like?
So what you are asking is that
photo.file --> photo.attachment.file
photo.file.url --> photo.attachment.file.url
photo.file.width --> photo.attachment.width
You can't solve this with delegates, because you want that file to mean different things based on what follows next. To achieve this you would need to reopen paperclip, which i would not recommend (because i believe the api is good the way it is).
The only way i can think of to solve this, is to add eliminate the file level too. Like so:
photo.width --> photo.attachment.width
photo.file --> photo.attachment.file
photo.url --> photo.attachment.file.url
This you could then solve by using a delegate for each of the wanted methods.
So you write
class Photo
delegate :width, :height, :file, :to => :attachment
delegate :url, :to => :'attachment.file'
end
Hope this helps.
You can use Rails 'delegate' method. Have a look at my answer for this question:
What is a more Ruby-like way of doing this command?
The simplest way that comes to mind is to delegate url method in attachment to file:
class Attachment < ActiveRecord::Base
delegate :url, :to => :file
end
This way you can call photo.attachment.url, photo.attachment.width, photo.attachment.height, which for me seems pretty consistent. You could optionally alias attachment to file - this way you'd get the exact method names you asked for (photo.file.width, photo.file.url), but I would not recommend that, because it seems confusing (calling an attachment "file").
class Photo < ActiveRecord::Base
def file
attachment
end
end
With plain Ruby you can use Forwardable:
require 'forwardable'
class RecordCollection
attr_accessor :records
extend Forwardable
def_delegator :#records, :[], :record_number
end