how attr_accessor works in ActiveResource rails 3? - ruby-on-rails

How does attr_accessor works in ActiveResource?
class User < ActiveResource::Base
attr_accessor :name
end
How its different from attr_accessor in ActiveRecord?

attr_accessor is built into Ruby, not rails. You may be confusing it with attr_accessible, which is part of ActiveRecord. Here's the difference:
attr_accessor
Take a class:
class Dog
attr_accessor :first_name, :last_name
def initialize(first_name, last_name)
self.first_name = first_name
self.last_name = last_name
end
end
attr_accessor creates a property and creates methods that allow it to be readable and writeable. Therefore, the above class would allow you to do this:
my_dog = Dog.new('Rex', 'Thomas')
puts my_dog.first_name #=> "Rex"
my_dog.first_name = "Baxter"
puts my_dog.first_name #=> "Baxter"
It creates two methods, one for setting the value and one for reading it. If you only want to read or write, then you can use attr_reader and attr_writer respectively.
attr_accessible
This is an ActiveRecord specific thing that looks similar to attr_accessor. However, it behaves very differently. It specifies which fields are allowed to be mass-assigned. For example:
class User
attr_accessible :name, :email
end
Mass assignment comes from passing the hash of POST parameters into the new or create action of a Rails controller. The values of the hash are then assigned to the user being created, e.g.:
def create
# params[:user] contains { name: "Example", email: "..."}
User.create(params[:user])
#...
end
For the sake of security, attr_accessible has to be used to specify which fields are allowed to be mass-assigned. Otherwise, if the user had an admin flag, someone could just post admin: true as data to your app, and make themselves an admin.
In summary
attr_accessor is a helper method for Ruby classes, whereas attr_accessible is an ActiveRecord thing for rails, to tighten up security.

You don't need to have attr_accessor to work with ActiveResource.
The base model (ActiveResource::Base) contains the #attributes hash in which you can 'dump' properties as you wish. (you should be careful though on what params you allow)
The way it does this, is by handling the method_missing? method.
You can take a look here
If you define attr_accessor, what ruby does is that it creates a setter and a getter method, so it will break the method_missing functionality since it will never get to execute that code.
If you still want to use attr_accessor, you should create a Concern something like this:
module Attributes
extend ActiveSupport::Concern
module ClassMethods
def attr_accessor(*attribs)
attribs.each do |a|
define_method(a) do
#attributes[a]
end
define_method("#{a}=") do |val|
#attributes[a] = val
end
end
end
end
end

Related

Set non-database attribute for rails model without `attr_accessor`

In PHP, I can set an attribute (that is not a column in database) to a model. E.g.(PHP code),
$user = new User;
$user->flag = true;
But in rails, when I set any attribute that doesn't exist in database, it will throw an error undefined method flag. There is attr_accessor method, but what will happen if I need about ten temp attributes?
but what will happen if I need about ten temp attributes?
#app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :flag, :other_attribute, :other_attribute2, :etc...
end
attr_accessor creates "virtual" attributes in Rails -- they don't exist in the database, but are present in the model.
As with attributes from the db, attr_accessor just creates a set of setter & getter (instance) methods in your class, which you can call & interact with when the class is initialized:
#app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :flag
# getter
def flag
#flag
end
# setter
def flag=(val)
#flag = val
end
end
This is expected because it's how ActiveRecord works by design. If you need to set arbitrary attributes, then you have to use a different kind of objects.
For example, Ruby provides a library called OpenStruct that allows you to create objects where you can assign arbitrary key/values. You may want to use such library and then convert the object into a corresponding ActiveRecord instance only if/when you need to save to the database.
Don't try to model ActiveRecord to behave as you just described because it was simply not designed to behave in that way. That would be a cargo culting error from your current PHP knowledge.
As the guys explained, attr_accessor is just a quick setter and getter.
We can set our Model attr_accessor on record initializing to be a Ruby#Hash for example using ActiveRecord#After_initilize method so we get more flexibility on temporarily storing values (idea credit to this answer).
Something like:
class User < ActiveRecord::Base
attr_accessor :vars
after_initialize do |user|
self.vars = Hash.new
end
end
Now you could do:
user = User.new
#set
user.vars['flag'] = true
#get
user.vars['flag']
#> true
All that attr_accessor does is add getter and setter methods which use an instance variable, eg this
attr_accessor :flag
will add these methods:
def flag
#flag
end
def flag=(val)
#flag = val
end
You can write these methods yourself if you want, and have them do something more interesting than just storing the value in an instance var, if you want.
If you need temp attributes you can add them to the singleton object.
instance = Model.new
class << instance
attr_accessor :name
end

Filtering fields from ActiveRecord/ActiveModel JSON output (by magic!)

I want to filter out specific fields from ActiveRecord/ActiveModel classes when outputting JSON.
The most straightforward way to do this is just overriding as_json, perhaps like so:
def as_json (options = nil)
options ||= {}
super(options.deep_merge({:except => filter_attributes}))
end
def filter_attributes
[:password_digest, :some_attribute]
end
This works, but it's a little verbose and lends itself to not being DRY pretty fast. I thought it would be nice to just declare the filtered properties with a magical class method. For example:
class User < ActiveRecord::Base
include FilterJson
has_secure_password
filter_json :password_digest
#...
end
module FilterJson
extend ActiveSupport::Concern
module ClassMethods
def filter_json (*attributes)
(#filter_attributes ||= Set.new).merge(attributes.map(&:to_s))
end
def filter_attributes
#filter_attributes
end
end
def as_json (options = nil)
options ||= {}
super(options.deep_merge({:except => self.class.filter_attributes.to_a}))
end
end
The problem with this is getting it to deal with inheritance properly. Let's say I subclass User:
class SecretiveUser < User
filter_json :some_attribute, :another_attribute
#...
end
Logically, it makes sense to filter out :some_attribute, :another_attribute, and also :password_digest.
However, this will only filter the attributes declared on the class. To the desired end, I tried to call super within filter_attributes, but that failed. I came up with this, and it's a hack.
def filter_attributes
if superclass.respond_to?(:filter_attributes)
superclass.filter_attributes + #filter_attributes
else
#filter_attributes
end
end
This is obviously brittle and not idiomatic, but there's the "what" that I'm trying to accomplish.
Can anyone think of a way to do it more correctly (and hopefully more elegantly)? Thanks!
I think it is a safer solution to white-list attributes than to black-list them. This will prevent unwanted future attributes added to User or SomeUser from making it into your JSON response because you forgot to add said attributes to filter_json.
You seem to be looking for a solution to your specific inheritance issue. I'm still going to point out active_model_serializers, as I feel it is a saner way to manage serialization.
class UserSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name
end
class SecretUserSerializer < UserSerializer
attributes :secret_attribute, :another_attribute
end
Given some SecretUser s you can do
SecretUserSerializer.new(s).as_json
and you'll get :id, :first_name, :last_name, :secret_attribute, and :another_attribute. The inheritance works as expected.

Unable to initialize ActiveRecord object

I'm trying to do in a rails console
>> user = User.new(:name => "", :email => "test#example.com")
=> #<User not initialized>
My User class looks like
class User < ActiveRecord::Base
attr_accessor :name, :email
has_many :microposts
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
def formatted_email
"#{#name} <#{#email}>"
end
end
I am following along from the rails tutorial. Why am I not able to initialize the object ?
tl;dr: Copy exactly from the book and you should be fine. (Note: I am the author.)
The example in question is from Chapter 4 of the Ruby on Rails Tutorial book and is not an Active Record model. In particular, the User class shown in the question is based on Listing 4.9:
class User
attr_accessor :name, :email
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
def formatted_email
"#{#name} <#{#email}>"
end
end
This class does not inherit from ActiveRecord::Base, but rather must be included explicitly in the console using require './example_user.rb', as described in Section 4.4.5. The behavior you're seeing is the result of including < ActiveRecord::Base in the first line, but if you copy the code in Listing 4.9 exactly you should see the expected behavior.
are you running your console in the same file directory as your project? I'd also try switching up the  notation to the example used in the book and see if that gets you anywhere.
you can also try calling User.new with no attributes and see if it generates an object as listed in 6.1.3 of the tutorial , and then fill in the attributes and see if it works.
also make sure you dont have a validation on your user name in your model.
and a last check you can run user.error to see why it might not be saving
First, I assume that User model persists in your Rails app. That means, that you already have a migrated User model before running rails console.
If that table doesn't exist, you will be instanly prompted with:
=> User(Table doesn't exist)
Now, let's have some fun in rails console:
First things first, don't override initialize method in Rails model; While creating an object initialize method from ActiveRecord takes precedence (I think), so it may create conflicts. Instead use after_initialize callback. In console:
class User < ActiveRecord::Base
attr_accessible :name, :email
def after_initialize(attributes = {})
self[:name] = attributes[:name]
self[:email] = attributes[:email]
end
def formatted_email
"#{self.name} <#{self.email}>"
end
end
Now,
u = User.new({name: "Foo", email: "foo#bar.org"})
#<User name: "Foo", email: "foo#bar.org", created_at:nil updated_at: nil>
u.formatted_email
#=> "Foo <foo#bar.org>"
All done! Sweet.
UPDATE:
As per your recent gist; I see no point of having after_initialize at all. Rails does that on it's own.
First thing first, replace attr_accessor with attr_accessbile.
attr_accessor is ruby method(courtesy, metaprogramming) which creates getter and setter for provided instance variable. Rails uses attr_accessible for that; for security concerns, only instance variables allowed in attr_accessible allowed for mass-assignment (by sending params hash).
user.rb
class User < ActiveRecord::Base
attr_accessible :name, :email
#def after_initialize(attributes = {})
# self[:name] = attributes[:name]
# self[:email] = attributes[:email]
#end
def formatted_email
"#{self.name} <#{self.email}>"
end
end
Are you running console using the rails c command to load your environment from the root directory of your project? Typing irb to start a console session does not load the Rails application environment by itself.
Here are some more troubleshooting tips
Check to make sure that the development database specified in config/database.yml is running
Check to make sure a migration exists to create the Users table
Check to make sure the migrations have run with rake db:migrate
Check to make sure that a Users table actually does exist in the database, with columns of type varchar (or text) for fields :name and :email

When overriding an ActiveRecord Model, and defining a new attr_accessible, how do I add without duplicating?

When I am using a Rails Engine, and desiring to override and add to its behavior, I have faced the following problem:
Say the Engine has a ActiveRecord model named Course
module MyEngine
class Course < ActiveRecord::Base
attr_accessible :name, :description, :price
end
end
And I want to create a migration in my main Rails app, to add a column to it, and I need to add that new column to the attr_accessible (so it can be mass assigned)
MyEngine::Course.class_eval do
attr_accessible :expiration_date
end
But then Rails complains that the first 3 attrs are not Mass-Assignable, so instead of just "adding" the new attribute to the override, I have to re-declare all the attributes in the overridden class, like:
MyEngine::Course.class_eval do
attr_accessible :name, :description, :price, :expiration_date
end
Is there a better way to not re-declare these attributes, and just add the new attribute?
By looking at the source code:
# File activemodel/lib/active_model/mass_assignment_security.rb, line 174
def attr_accessible(*args)
options = args.extract_options!
role = options[:as] || :default
self._accessible_attributes = accessible_attributes_configs.dup
Array.wrap(role).each do |name|
self._accessible_attributes[name] = self.accessible_attributes(name) + args
end
self._active_authorizer = self._accessible_attributes
end
You could try to use one of the internal data structures to recover the attributes that have already been defined, so you don´t duplicate code, or you can hack your way and create a new method that let´s you "append" values to attr_accessible. But there is no code baked for that yet.

Validation in Rails without a model

I have a form that allows the user to send a message to an email, and I want to add validation to it. I do not have a model for this, only a controller. How should I do this in Rails?
I was considering doing the validation in the controller, and displaying the errors to the user using the flash object. Is there a better way of doing this?
The best approach would be to wrap up your pseudo-model in a class, and add the validations there. The Rails way states you shouldn't put model behavior on the controllers, the only validations there should be the ones that go with the request itself (authentication, authorization, etc.)
In Rails 2.3+, you can include ActiveRecord::Validations, with the little drawback that you have to define some methods the ActiveRecord layer expects. See this post for a deeper explanation. Code below adapted from that post:
require 'active_record/validations'
class Email
attr_accessor :name, :email
attr_accessor :errors
def initialize(*args)
# Create an Errors object, which is required by validations and to use some view methods.
#errors = ActiveRecord::Errors.new(self)
end
# Required method stubs
def save
end
def save!
end
def new_record?
false
end
def update_attribute
end
# Mix in that validation goodness!
include ActiveRecord::Validations
# Validations! =)
validates_presence_of :name
validates_format_of :email, :with => SOME_EMAIL_REGEXP
end
In Rails3, you have those sexy validations at your disposal :)
For Rails 3+, you should use ActiveModel::Validations to add Rails-style validations to a regular Ruby object.
From the docs:
Active Model Validations
Provides a full validation framework to your objects.
A minimal implementation could be:
class Person
include ActiveModel::Validations
attr_accessor :first_name, :last_name
validates_each :first_name, :last_name do |record, attr, value|
record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
end
end
Which provides you with the full standard validation stack that you
know from Active Record:
person = Person.new
person.valid? # => true
person.invalid? # => false
person.first_name = 'zoolander'
person.valid? # => false
person.invalid? # => true
person.errors.messages # => {first_name:["starts with z."]}
Note that ActiveModel::Validations automatically adds an errors method
to your instances initialized with a new ActiveModel::Errors object,
so there is no need for you to do this manually.

Resources