I am playing around with Devise in a project, and am just trying to better understand how it all works. The Sessions controller in particular is doing a few things that I don't understand:
class Devise::SessionsController < ApplicationController
def new
# What benefit is this providing over just "resource_class.new"?
self.resource = resource_class.new(sign_in_params)
clean_up_passwords(resource)
# What is "serialize_options" doing in the responder?
respond_with(resource, serialize_options(resource))
end
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
...
protected
...
def serialize_options(resource)
methods = resource_class.authentication_keys.dup
methods = methods.keys if methods.is_a?(Hash)
methods << :password if resource.respond_to?(:password)
{ :methods => methods, :only => [:password] }
end
def sign_in_params
devise_parameter_sanitizer.sanitize(:sign_in)
end
end
I assume that these methods are adding some sort of security. I'd just like to know what exactly they are protecting against.
The implementation of devise_parameter_sanitizer is creating a ParameterSanitizer instance. I often find looking at tests to be helpful in understanding the purpose of code; and this test I think illustrates it best-- since you're creating a new user, you don't want to allow users to assign any value they want to any parameter they want, so sanitize means "strip out any attributes other than the ones we need for this action". If this wasn't here, and you had a role attribute, a user could send a specially-crafted POST request to make themselves an admin when signing up for your site. So this protects against what's called, in the general case, a mass assignment vulnerability. Rails 3 and Rails 4 protect against this in different ways but the protections can still be turned off, and I'm guessing Devise is trying to set some good-practice defaults.
The serialize_options method is creating a hash of options to support rendering to XML or JSON. I found this out by looking at the implementation of responds_with, which calls extract_options! which uses the last argument as options if the last argument is a Hash. The documentation for responds_with says "All options given to #respond_with are sent to the underlying responder", so I looked at ActionController::Responder, whose documentation explains the process it takes to look for a template that matches the format, then if that isn't found, calling to_#{format}, then calling to_format. There's a view for HTML, and ActiveRecord objects respond to to_xml and to_json. Those methods use those options:
The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
attributes included, and work similar to the +attributes+ method.
To include the result of some method calls on the model use <tt>:methods</tt>.
So this keeps you from exposing more information than you might want to if someone uses the XML or JSON format.
Related
In Uncle Bob's clean architecture, which Hanami is inspired by, Form Objects guard the boundary between Interactors and our delivery mechanism (typically an http endpoint).
In the Hanami docs, bounary guarding is done using params blocks in Actions (see here). This seems to couple validation to the http delivery mechanism. It seems more natural to me that Form Objects (or params black which accomplish the same thing) would live in delivery-mechanism-agnostic Interactors.
Unfortunately, I cannot figure out if Hanami supports such a design. I found a similar question on the Hanami forum, but it has no answer.
To clarify, below is a snippet of code demonstrating the kind of thing I want to do, but using Virtus and ActiveModel::Validations rather than Hanami facilities. For those familiar with Trailblazer, its contract block within its operations (its term for Interactor) is another example.
Finally, am I misunderstanding something about the intended use of Hanami? Hanami supports Interactors, so it seems like this should be possible...
require 'hanami/model'
require 'active_model'
require 'virtus'
class PersonForm
include Virtus.model
include ActiveModel::Validations
attribute :name, String
attribute :age, Integer
validates :name, :age, presence: true
def each
attributes.each {|a| yield a}
end
end
class Person
include Hanami::Entity
attributes :name, :age
end
# Code like this would then live inside of an Interactor, which
# can accept a params hash.
jonah_form = PersonForm.new({name: 'Jonah', age: '99'})
jonah = Person.new(jonah_form) if jonah_form.valid?
p jonah #=> <Person:0x007fbdde1edcc0 #id=nil #name="Jonah" #age=99>
# do stuff with our jonah entity
sorry for missing this question.
The params act as validator to save developers to create and instantiate another class. In Ruby Community this is a problem.
DSL to the rescue for this compromise.
If you prefer to have a concrete form object, instead of using Virtus and ActiveModel, you can just include Hanami::Validations and have the same behavior.
In Rails, I have created a Model that retrieves users from an LDAP database rather than from ActiveRecord. Now I am attempting to integrate my ActiveRecord models with the LDAP-based models, so I am writing methods in my models that emulate some common ActiveRecord methods.
One of the methods I am trying to emulate is one that is normally created by the has_many through relationship on ActiveRecord. In ActiveRecord, this relationship would allow the following:
user = User.first
groups = user.groups # == Array of Groups
groups << Group.create(name: "Test") # How does Rails allow this?
How exactly does Rails allow this? I've tried dynamically assigning methods to the array instance returned by user.groups, but there doesn't seem to be any way to make those methods aware of which user record the array was created from. (So they can assign user_id on the new relationship record.) What am I missing?
Though user.groups appears to be an array of groups, it's actually an entirely separate class -- a Rails internal class that you usually don't know much about called an association proxy. The proxy responds to methods like <<, create, new and so on by proxying requests to the target class and then setting the association appropriately.
If you want similar functionality you'll have to implement your own kind of proxy associations. Doing so will be pretty complicated, but this might get you started.
module LDAP
class Association
attr_accessor :source, :target
def initialize(source, target)
#source = source
#target = target
end
def <<(obj)
#source.group_ids = [group_ids + obj].flatten.uniq
#source.save
end
end
end
class User
def groups
LDAP::Association.new(self, Group)
end
end
This is not even particularly close to how ActiveRecord implements association proxies. However, this is quite a bit simpler than ActiveRecord's solution and should be enough to duplicate some basic ActiveRecord functionality.
I would go about doing this by peeking into the Rails Source Code, e.g. the code for the
Group.create example above can be found in
http://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html
def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, options, &block) }
else
object = new(attributes, options, &block)
object.save
object
end
end
end
I'm relatively new to testing and very new to Rails 4 and rSpec. I am trying to test a controller that uses Devise for authentication and I am stuck. All of the examples I can find are for Rails 3.
I'm using Rails 4.0.3, Devise 3.2.3, rSpec 2.14.1 and FactoryGirl 4.4.0.
class LessonPlansController < ApplicationController
before_action :authenticate_user!
# GET /lesson_plans
def index
#lesson_plans = current_user.lesson_plans.to_a
end
.
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_lesson_plan
#lesson_plan = LessonPlan.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def lesson_plan_params
params[:lesson_plan]
end
def lesson_plan_params
params.require(:lesson_plan).permit(:title, :synopsis)
end
end
Here are my factory definitions: (Maybe I don't need to define user_id in the lesson_plan factory?)
FactoryGirl.define do
factory :user do
sequence( :username ) { |n| "user#{n}" }
sequence( :email ) { |n| "foo#{n}#example.com" }
password 'foobarbaz'
password_confirmation 'foobarbaz'
created_at Time.now
updated_at Time.now
end
end
FactoryGirl.define do
factory :lesson_plan do
user_id 1
title "The French Revolution"
synopsis "Background and events leading up to the French Revolution"
end
end
And the test part is where I get stuck.
describe LessonPlansController do
let(:valid_attributes) { { } }
let(:valid_session) { {} }
# describe "GET index" do
it "assigns all lesson_plans as #lesson_plans" do
user=FactoryGirl.create(:user)
sign_in user
lesson_plan = LessonPlan.create! valid_attributes
get :index, {}, valid_session
assigns(:lesson_plans).should eq([lesson_plan])
end
end
I'm not sure what to put in valid_attributes and valid_session (or if I even need them). The test will get as far as signing in the user, but will fail on creation of the lesson_plan. Admittedly this is the default/generated test for rSpec, but I am not sure how to proceed.
Examples I have seen use a before block to set up the user. I haven't been able to find anything on the Devise wiki page covering how to write basic rSpec tests for a controller that requires the user to be logged in. Any pointers would be greatly appreciated!
"I'm not sure what to put in valid_attributes and valid_session (or if I even need them)."
Well that depends what you're testing for.. Say you're testing validations & want to ensure that a record not be created if x column is set to null... then you could try to specifically create a record with invalid attributes (e.g. column: nil) and expect the result to not return true; maybe you want to ensure that it IS created with valid attributes.
You can btw, use `attributes_for(:factory_name)`` since you're using FactoryGirl. And no you don't necessarily need to specify the user's id in your lesson plan factory; unless you always want it to reference user 1. You can simply reference user with no value. Check out http://everydayrails.com/2012/03/12/testing-series-intro.html and especially parts 3-5 for an introduction to testing with RSPec.. I found this a pretty easy to follow guide when I was getting started.
Well, DRY! So i thought it should be easy to add a new action (like the existing new, edit) to all my controllers (in my case copy). But how do you setup a new route for ALL controllers?
Without going in to 'loops' (i.e. %w().each ...) inside the routes.rb ?
I mean, we want DRY right? So you don't want copy your action inside the routes file for each resource. I guess you should be able to extend the default actions/routes (index, new, edit,etc.) easy?
Thanks!
AFIK no way to do this by default. You could monkey-patch resources to include this functionality:
https://github.com/rails/rails/blob/b229bc70e50ec0887c5bb3aaaa9c6ee8af054026/actionpack/lib/action_dispatch/routing/mapper.rb#L982
...but my hunch is you would be better off re-considering whether this functionality can be created another way, since what you want to do is "off the Rails".
One option is create a CloneController#new that accepts a model and id and creates a clone. This seems like it would be drier, and wouldn't require you to pepper a gazillion "clone_article" "clone_blog" "clone_user" paths all over the place.
Obviously you would want to carefully white-list the models/ids that can be passed in.
Looking through the source there isn't a way to add to the default actions for a resource.
But, as #juwiley says, the methods resources :item is just a shortcut for creating a load of member and collection methods.
All you need to do is something like this
class ActionDispatch::Routing::Mapper
def resources_with_copy(*resources, &block)
block_with_copy = lambda do
block.call
member do
post :copy
end
end
resources(*resources, &block_with_copy)
end
end
Then in your routes.rb just say
resources_with_copy :items
resources_with_copy :posts do
member do
post :share
end
end
...
Preamble:
I investigated how to version an API and found several ways to do it. I decided to try peter williams' suggestion and created new vendor mime types to specify version and format. I could find no definitive write-up for doing this following "the rails way" so I pieced together info from several places. I was able to get it working, but there is some goofiness in the way the renderers handle Widget array vs Widget instance in respond_with.
Basic steps & problem:
I registered mime types and added renderers for version 1 in both xml and json to ApplicationController, the renderers call to_myproj_v1_xml and to_myproj_v1_json methods in the model. respond_with(#widget) works fine but respond_with(#widgets) throws an HTTP/1.1 500 Internal Server Error saying that the "Template is missing".
Workaround:
"Template is missing" means that no render was called and no matching template exists. by accident, I discovered that it is looking for a class method... so I came up with the code below which works but I'm not really happy with it. The goofiness is mostly in and related to xml = obj.to_myproj_v1_xml(obj) and the duplication in the model.
My question is - has anyone done anything similar in a slightly cleaner fashion?
-= updated code =-
config/initializers/mime_types.rb:
Mime::Type.register 'application/vnd.com.mydomain.myproj-v1+xml', :myproj_v1_xml
Mime::Type.register 'application/vnd.com.mydomain.myproj-v1+json', :myproj_v1_json
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate
ActionController.add_renderer :myproj_v1_xml do |obj, options|
xml = obj.to_myproj_v1_xml
self.content_type ||= Mime::Type.lookup('application/vnd.com.mydomain.myproj-v1+xml')
self.response_body = xml
end
ActionController.add_renderer :myproj_v1_json do |obj, options|
json = obj.to_myproj_v1_json
self.content_type ||= Mime::Type.lookup('application/vnd.com.mydomain.myproj-v1+json')
self.response_body = json
end
end
app/models/widget.rb:
class Widget < ActiveRecord::Base
belongs_to :user
V1_FIELDS = [:version, :model, :description, :name, :id]
def to_myproj_v1_xml
self.to_xml(:only => V1_FIELDS)
end
def to_myproj_v1_json
self.to_json(:only => V1_FIELDS)
end
def as_myproj_v1_json
self.as_json(:only => V1_FIELDS)
end
end
app/controllers/widgets_controller.rb:
class WidgetsController < ApplicationController
respond_to :myproj_v1_xml, :myproj_v1_json
def index
#widgets = #user.widgets
respond_with(#widgets)
end
def create
#widget = #user.widgets.create(params[:widget])
respond_with(#widget)
end
def destroy
#widget = #user.widgets.find(params[:id])
respond_with(#widget.destroy)
end
def show
respond_with(#widget = #user.widgets.find(params[:id]))
end
...
end
config/initializers/monkey_array.rb
class Array
def to_myproj_v1_json(options = {})
a = []
self.each { |obj| a.push obj.as_myproj_v1_json }
a.to_json()
end
def to_myproj_v1_xml(options = {})
a = []
self.each { |obj| a.push obj.as_myproj_v1_json } # yes this is json instead of xml. as_json returns a hash
a.to_xml()
end
end
UPDATE:
Found another solution that feels better but still a little weird (I'm still not completely comfortable with monkey patches), probably ok though... basically moved building the response data from the class method to_myproj_v1_json to a monkey patch on Array. This way when there is an Array of Widgets, it calls the instance method as_myproj_v1_json on each Widget and returns the whole Array as desired format.
One note:
as_json has nothing to do with json format, just creates a hash. Add custom formatting to as_myproj_v1_json (or an as_json override if you aren't using custom mime types), then to_json will change a hash to a json string.
i have updated the code below to be what is currently used, so the original question may not make sense. if anyone wants the original question and code shown as was and fixed code in a response i can do that instead.
For the answer: see the question :-)
In short, there are different solutions, of which one is in the question above:
Monkey-patch Array to implement a method that will give the (old) v1 JSON back
I haven't seen this content type trick used anywhere in a Rails project before so this is new to me. The way I've typically seen it done is to define a route namespace (e.g. /api/v1/) which goes to a controller (say, Api::Version1Controller).
Also, I know you want to do things the "Rails way", and maybe this sounds crotchety coming from a guy who has been with Rails since 1.3, but the whole respond_with / respond_to stuff is rather magic to me. I didn't know that respond_to looks for a to_XXX method when it serializes objects, for instance (maybe I need to read up on that). Having to monkey-patch Array like that seems rather silly. Besides, for an API, formatting the model data is really the view's job, not the model's. I might look into something like rabl in this case. There's a good writeup about it here.