I'd like to be able to obfuscate the id's of objects in my app so that a URL can be shared, but not easily guessed.
I've gathered that I need to store a hashed_id in the database for the object, and that it may be possible to rewrite the to_param function to use that hashed_idin the URL but I'm having trouble getting it to work.
model:
after_create :create_hashed_id
def to_param
self.hashed_id
end
def create_hashed_id
self.update_attributes(:hashed_id => Digest::SHA1.hexdigest([Time.now, rand].join)[0,16])
end
controller:
def show
#upload = Upload.find_by_hashed_id(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #upload }
end
end
The object gets created with a hash_id attribute but when I try and invoke the show method it looks like it's looking in the id column instead of the hashed_id column.
Related
I'm working with validations in rails, stuff like:
validates_presence_of :some_field
I've noticed that if the validation fails, all changes are overwritten with existing values from the database. This makes some sense, as the page is basically being reloaded (as I gather from my development log), however this increases the risk of user error/frustration, as a single error in one field will require the hapless fellow to re-enter the changes he made to all fields.
My question: How can I get rails to reload the data that was just submitted if validation fails? That way, the user can correct the mistake without needing to re-enter the rest of his revisions.
Thanks for any advice.
Edit:
My update method, as requested, is as follows:
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
if #company.update(company_params)
redirect_to incorporations_index_path
else
redirect_to edit_incorporation_path(#incorporation)
end
end
Full disclosure regarding my controller: the above update is from my incorporations_controller even though I'm updating my Company model. Company has_one :incorporation. I did this because, in the larger context of my app, it made my associations much cleaner.
Update your controller to this
def update
#incorporation = Incorporation.find(params[:id])
#company = #incorporation.company
begin
#company.name="#{params[:company][:names_attributes].values.first["name_string"]} #{params[:company][:names_attributes].values.first["suffix"]}"
rescue NoMethodError
#company.name="Company #{#company.id} (Untitled)"
end
respond_to do |format|
if #company.update(company_params)
format.html { redirect_to({:action => "index"})}
else
format.html{render :edit}
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
To add to the correct answer, you can clean up your code quite a bit:
def update
#incorporation = Incorporation.find params[:id]
respond_to do |format|
if #incorporation.update company_params
format.html { redirect_to({:action => "index"})}
else
format.html { render :edit }
format.json { render json: #incorporation.errors, status: :unprocessable_entity }
end
end
end
If you're using accepts_nested_attributes_for, you definitely should not hack the associated objects on the front-end.
You should look up fat model, skinny controller (let the model do the work):
#app/models/company.rb
class Company < ActiveRecord::Base
before_update :set_name
attr_accessor :name_string, :name_suffix
private
def set_name
if name_string && name_suffix
self[:name] = "#{name_string} #{name_suffix}"
else
self[:name] = "Company #{id} (Untitled)"
end
end
end
This will allow you to populate the name of the `company. To edit your nested/associated objects directly is an antipattern; a hack which will later come back to haunt you.
The key from the answer is: render :edit
Rendering the edit view means that your current #company / #incorporation data is maintained.
Redirecting will invoke a new instance of the controller, overriding the #incorporation, hence what you see on your front-end.
It's the first time I'm using this gem and it's driving me crazy with something as simple as authorize the showaction only for the resource owner.
I tried different ways, configuring the controller mapping and actions, but always get the unauthorized message for show, other actions work as they should.
It seems that showis not getting it's way to the ApplicationAuthorizer.
This is how it's configured:
class EnterpriseAuthorizer < ApplicationAuthorizer
# This works
def self.creatable_by?(user)
user.is_enterpriser?
end
# This doesn't
def readable_by?(user)
true # Just for testing
end
end
class EnterprisesController < ApplicationController
authorize_actions_for Enterprise
def show
#enterprise = Enterprise.find(params[:id])
respond_to do |format|
format.html
format.json { render json: #enterprise }
end
end
I have include Authority::UserAbilities in User and include Authority::Abilities in the Enterprise model. And User has_one :enterprise
Any idea? Thinking seriously about rolling back to cancan.
Thanks in advance.
Authority has different ways of checking permissions. For collection-based actions (e.g. new, create, index), you use authorize_actions_for Model.
For instance-based actions (e.g. edit, update, show, delete), you must call authorize_action_for #instance.
Change your code to this and it should work.
class EnterprisesController < ApplicationController
authorize_actions_for Enterprise
def show
#enterprise = Enterprise.find(params[:id])
authorize_action_for #enterprise
respond_to do |format|
format.html
format.json { render json: #enterprise }
end
end
end
If you want a less messy way to do this, put the
#enterprise = Enterprise.find(params[:id])
authorize_action_for #enterprise
into a before filter that's called by each instance action.
I'm new to Rails, and am trying to make a pet app. It has 3 attributes: name, hungry, and mood. I generated a scaffold and wrote a feed method into the model:
def feed
self.hungry==false;
save!
end
I want feed to be something a user can do in the edit view, so I created a checkbox to indicate feeding vs. not feeding. My plan was to call the feed function from the controller in the update function. Right now, it looks like this:
def update
respond_to do |format|
if #pet.update(pet_params)
format.html { redirect_to #pet, notice: 'Pet was successfully updated. #{params[:feed]}' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #pet.errors, status: :unprocessable_entity }
end
end
if #pet.update_attributes(params[:feed])
#pet.feed
end
end
I have an odd sense that I'm mixing metaphors here, but am not sure of the right course of action. I'm trying to call a function from my update function, and that doesn't seem to be working. It might have to do with the fact that "feed" isn't listed in my model's parameters, but I don't need it to be. I just need it to call a function. Help!
Your method definition is wrong. Instead of assigning a value, you are comparing equality.
def feed
self.hungry == false; # only one = should be used.
save!
end
There is a better way to do this, however:
class Pet
attr_accessor :feed_me
before_save :feed
def feed
hungry = false if feed_me
end
end
You should not need the controller check:
if #pet.update_attributes(params[:feed])
#pet.feed
end
Which is wrong, by the way. You need to check if the param[:feed] exists, not if the pet objet has updated correctly.
For this solution to work, you would need to add an attribute to your form:
= f.check_box :feed_me
Another way to do this would be to map the hungry attribute to the checkbox and just name the label feed:
= f.label :hungry, "Feed"
= f.checkbox :hungry
You could then go ahead and just remove the before_save, the attr_accessor, and the method self.feed.
I want to output a list of affiliate links, each tagged to identify the current user. It would be simple in HTML, but we're writing an API, so the output is JSON.
I have it working, but it seems overly complicated. Is this the best approach?
My model, AffiliateLink contains a field (the raw HTML of the link) that I'll transform and output on the fly by adding a token. I have a model method that produces the replacement -- it is non-trivial because we use multiple affiliates and each has a special transformation rule that this method knows about:
def link_with_token(user_token)
# some gnarly code that depends on a lot of stuff the model knows
# that returns a proper link
end
To get my correct link html in JSON I have done these things:
add attr_accessor :link_html to model
add an instance method to set the new accessor
...
def set_link_html(token)
self.link_html = link_with_tracking_token(token)
end
override as_json in the model, replacing the original html_code with link_html
...
def as_json(options = {})
super(:methods => :link_html, :except => :html_code)
end
iterate over the collection returned in the controller method to do the transformation
...
def index
#links = Admin::AffiliateLink.all # TODO, pagination, etc.
respond_to do |format|
format.html # index.html.erb
format.json do
#links.each do |link|
link.set_link_html(account_tracking_token)
end
render json: #links
end
end
end
This seems like a lot of stuff to do just to get my teensy-weensy transformation done. Helpful suggestions (relating to this problem and not to other aspects of the code, which is in flux now) are welcome.
1) A quick solution to your problem (as demonstrated here):
affiliate_links_controller.rb
def index
#links = Admin::AffiliateLink.all # TODO, pagination, etc.
respond_to do |format|
format.html # index.html.erb
format.json do
render json: #links.to_json(:account_tracking_token => account_tracking_token)
end
end
end
AffiliateLink.rb
# I advocate reverse_merge so passed-in options overwrite defaults when option
# keys match.
def as_json(options = {})
json = super(options.reverse_merge(:except => :html_code))
json[:link_with_token] = link_with_token(options[:account_tracking_token])
json
end
2) A more hardcore solution, if you're really writing an API:
See this article describing your problem.
See the gem that the authors made as a solution.
See this railscast on using the gem.
3) And lastly, the convenient solution. If you have a convenient model relation, this is clean:
Pretending AffiliateLink belongs_to :user. And assuming user_token is an accessible attribute of User.
AffiliateLink.rb
# have access to user.user_token via relation
def link_with_token
# some gnarly code that depends on a lot of stuff the model knows
# that returns a proper link
end
def as_json(options = {})
super(options.reverse_merge(:methods => :link_with_token, :except => :html_code))
end
affiliate_links_controller.rb
def index
#links = Admin::AffiliateLink.all # TODO, pagination, etc.
respond_to do |format|
format.html # index.html.erb
format.json do
render json: #links
end
end
end
I am using Thinking Sphinx to run searches and I get the appropriate ActiveRecord Models fine. The problem is, I want to create an appropriate link path and text on each model, then send the info to the browser in the form of JSON, via AJAX. I am using the following to build those link attributes:
In the controller:
class FindController < ApplicationController
def tag_results
#results = ThinkingSphinx.search(params[:terms])
#results.each do |result|
result.build_ajax_response
end
respond_to do |format|
format.html
format.json { render :json => #results }
end
end
end
In the model:
class TaggedItem < ActiveRecord::Base
attr_accessible :name
attr_accessor :search_link, :search_text
def build_ajax_response
self.search_link = Rails.application.routes.url_helpers.tagged_item_path(self.id)
self.search_text = self.name
end
end
The resulting json object doesn't have either of the search_* attributes listed, much less have a value for them. I've tried using #search_link as well as just search_link in the build_ajax_response method.
Am I doing this wrong? Could there be something else interfering?
Rails' default to_json doesn't know about those extra non active record attributes you've added. The easiest possible thing is probably to specify them as extra methods to include:
format.json { render :json => #results.to_json(:methods => [:search_link, :search_text]) }