I want to use REST in an Rails application that uses inherited_resources,
but I want certain properties not to be revealed during json and xml request.
Let's call that field 'password'.
I know I can overwrite the to_xml and to_json methods and then
super :except => [:password]
But I would have to do that for to_xml and to_json. Not very DRY.
Any ideas?
rest_member_defaults :except => [:password]
in the controller is vaguely what I'm aiming for.
Thanks!
I had this exact question and yours triggered me to wrap this up into a plugin hide_attributes which is also available as a gem.
Just add it to your Gemfile:
gem 'hide_attributes'
And then add something like this to your model:
class User < ActiveRecord::Base
hide_attributes :password, :password_salt
end
And there you go. Sorry that there are no tests yet and documentation is rather thin.
Related
I need some help with my plugin. I want to extend ActiveRecord::Base with a method that initializes another method that can be called in the controller.
It will look like this:
class Article < ActiveRecord::Base
robot_catch :title, :text
...
end
My attempt at extending the ActiveRecord::Base class with robot_catch method looks like following. The function will initialize the specified attributes (in this case :title and :text) in a variable and use class_eval to make the robot? function available for the user to call it in the controller:
module Plugin
module Base
extend ActiveSupport::Concern
module ClassMethods
def robot_catch(*attr)
##robot_params = attr
self.class_eval do
def robot?(params_hash)
# Input is the params hash, and this function
# will check if the some hashed attributes in this hash
# correspond to the attribute values as expected,
# and return true or false.
end
end
end
end
end
end
ActiveRecord::Base.send :include, Plugin::Base
So, in the controller, this could be done:
class ArticlesController < ApplicationController
...
def create
#article = Article.new(params[:article])
if #article.robot? params
# Do not save this in database, but render
# the page as if it would have succeeded
...
end
end
end
My question is whether if I am right that robot_catch is class method. This function is to be called inside a model, as shown above. I wonder if I am extending the ActiveRecord::Base the right way. The robot? function is an instance method without any doubt.
I am using Rails 3.2.22 and I installed this plugin as a gem in another project where I want to use this functionality.
Right now, it only works if I specifically require the gem in the model. However, I want it the functionality to be included as a part of ActiveRecord::Base without requiring it, otherwise I'd have to require it in every model I want to use it, not particularly DRY. Shouldn't the gem be automatically loaded into the project on Rails start-up?
EDIT: Maybe callbacks (http://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html) would be a solution to this problem, but I do not know how to use it. It seems a bit obscure.
First, I would suggest you make sure that none of the many many built in Rails validators meet your needs.
Then if that's the case, what you actually want is a custom validator.
Building a custom validator is not as simple as it might seem, the basic class you'll build will have this structure:
class SpecialValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# Fill this with your validation logic
# Add to record.errors if validation fails
end
end
Then in your model:
class Article < ActiveRecord::Base
validates :title, :text, special: true
end
I would strongly suggest making sure what you want is not already built, chances are it is. Then use resources like this or ruby guides resources to continue going down the custom validator route.
Answer
I found out the solution myself. Bundler will not autoload dependencies from a gemspec that my project uses, so I had to require all third party gems in an engine.rb file in the lib/ directory of my app in order to load the gems. Now everything is working as it should.
Second: the robot_catch method is a class method.
I know this version is still not officially released but I was checking out rc3 today and I noticed that I can no longer use Rails url helpers inside my serializers. In version 0.8.x, I could do the following:
class BrandSerializer < BaseSerializer
attributes :id, :name, :slug, :state
attributes :_links
def _links
{
self: api_v1_company_brand_path(object.company_id, object.id),
company: api_v1_company_path(object.company_id),
products: api_v1_company_brand_products_path(object.company_id, object.id)
}
end
end
But this is a no go in the new version. What's the best way of resolving this so that I can keep my links in my serializer?
Edit:
For now I'm doing the following but would love to hear if there's a more idiomatic way.
class BaseSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
If you add this to your ApplicationController or even probably to the controller generating the response:
serialization_scope :view_context
You can then use the view_context in the serialiser to access the URL helpers (or any view methods really).
Example: view_context.api_v1_company_brand_path(object.company_id, object.id)
I thought this was probably cleaner than including all those URL helpers etc... into the serialiser class.
including the library which had been excluded (as you had done) would most definitely be the shortest route (outside of revising the gem itself, in terms of idiomacy)
In my rails application I have a teams model. My route.rb file for teams looks like this:
resources :teams
In my teams_controller.rb file the line team_path(Team.first.id) works however the team_path url helper is not recognized in my model team.rb. I get this error message:
undefined local variable or method `team_path' for # <Class:0x00000101705e98>
from /usr/local/rvm/gems/ruby-1.9.3-p392/gems/activerecord-4.1.1/lib/active_record/dynamic_matchers.rb:26:in `method_missing'
I need to find a way for the model to recognize the team_path path helper.
You should be able to call the url_helpers this way:
Rails.application.routes.url_helpers.team_path(Team.first.id)
Consider solving this as suggested in the Rails API docs for ActionDispatch::Routing::UrlFor:
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
# you can do that by including Rails.application.routes.url_helpers in your class:
#
# class User < ActiveRecord::Base
# include Rails.application.routes.url_helpers
#
# def base_uri
# user_path(self)
# end
# end
#
# User.find(1).base_uri # => "/users/1"
In the case of the Team model from the question, try this:
# app/models/team.rb
class Team < ActiveRecord::Base
include Rails.application.routes.url_helpers
def base_uri
team_path(self)
end
end
Here is an alternative technique which I prefer as it adds fewer methods to the model.
Avoid the include and use url_helpers from the routes object instead:
class Team < ActiveRecord::Base
delegate :url_helpers, to: 'Rails.application.routes'
def base_uri
url_helpers.team_path(self)
end
end
Models are not supposed to be dealing with things like paths, redirects or any of that stuff. Those things are purely constructions of the view or the controller.
The model really should be just that; a model of the thing that you are creating. It should fully describe this thing, allow you to find instances of it, make changes to it, perform validations upon it... But that model wouldn't have any notion of what path should be used for anything, even itself.
A common saying in the Rails world is that if you're finding it difficult to do something (like call a path helper from a model) you are doing it wrongly. Take this to mean that even if something is possible, if it is hard to do in Rails it is likely not the best way to do it.
to add on the previous answer you can use Rails.application.routes.url_helpers just add in route :as like the following example:
get "sessions/destroy/:param_id", as: :logout
so you can use it as following:
Rails.application.routes.url_helpers.logout_path(:param_id => your_value)
Hopefully, this would help
Separate REST JSON API server and client?
I am looking for advice on how to consume my own API (like Twitter is said to) for an app that I plan on making.
I would like to have a REST API that I can then use for an web app, Android app and some analytics/dashboard apps.
Rails has a respond_with option, and I have seen some apps that have an html and json option, but I think that is a less than stellar way of doing things and json is data and html is for presentation, and you are not making use of your json API essentially
It seems stupid but how exactly would I use my REST api from Rails if I want to do a server side html solution? Using something like HTTParty seems like a lot of work, is there a way to access the API more directly (in ASP MVC for example you can instantiate a controller class and then call its methods for example.)
You can use HTTParty and create client model classes that resemble a rails models by including ActiveModel modules.
activeresource gem was used in previous rails versions, but they deprecated it in favor of HTTParty+ActiveModel like solutions.
Update
I have crafted this example (from memory) with the basic ideas, is not a full implementation but I think you will get the idea.
class Post
# Attributes handling
include Virtus
# ActiveModel
include ActiveModel::Validations
extend ActiveModel::Naming
include ActiveModel::Conversion
# HTTParty
include HTTParty
# Virtus attributes
attribute :id, Integer
attribute :title, String
attribute :content, Text # not sure about this one, read virtus doc
# Validations
validates :title, presence: true
def save
return false unless valid?
if persisted?
self.class.put("/posts/#{id}", attributes)
else
self.class.post("/posts", attributes)
end
end
# This is needed for form_for
def persisted?
# If we have an id we assume this model is saved
id.present?
end
def decorate
#decorator ||= PostDecorator.decorate(self)
end
end
gems needed:
httparty
activemodel (present in rails)
virtus
draper
I have a simple class I want to include in my Rails app, but I want to still be able to access all the Rails niceness, e.g use the gems, helpers, routes etc etc as normal.
How can I do this? Is it the right thing to do?
You didn't quite specify what the class is for. If it's a model, put it in the models folder. Don't forget that a model doesn't have to be backed by a database. If you have a class that represents something and has business logic, it's a model.
# models/report.rb
class Report
def self.attendance_for(course)
Enrollment.find :all, :include => [:sections], :conditions => ["sections.course_id = ?", course.id])
end
....
end
The lib folder is also appropriate, but depending on how you name the file and the class definition, you may still need to require it from environment.rb or elsewhere.
Another approach is to use a plugin or a gem to distribute your code.
you put it in the lib directory