I have the following code in my User model:
attr_protected :email
I'm trying to create a new user object, but I get a mass assignment protected error with the following code.
user = User.new(
:first_name => signup.first_name,
:last_name => signup.last_name,
:email => signup.email,
:birthday => signup.birthday,
:encrypted_password => signup.encrypted_password,
:salt => signup.salt
)
Does anyone know how I can work around the attr_protected to get this code to work and assign a value to email?
Thank you.
user = User.new(
:first_name => signup.first_name,
:last_name => signup.last_name,
:birthday => signup.birthday,
:encrypted_password => signup.encrypted_password,
:salt => signup.salt
)
user.email = signup.email
I literally just wrote a gem tonight to deal with this exact issue. I'm planning on adding it to RubyGems.org later this week after I give a presentation on it. Feel free to checkout the code in the mean time. http://github.com/beerlington/sudo_attributes
Using the gem, your code would change to:
user = User.sudo_new(
:first_name => signup.first_name,
:last_name => signup.last_name,
:email => signup.email,
:birthday => signup.birthday,
:encrypted_password => signup.encrypted_password,
:salt => signup.salt
)
You could also use sudo_create() if you want to save the instance
update_attributes now allows you to override the protection if you know the hash to be safe, say for internal use where the fields you're setting are not coming from user-controllable hash.
user = User.new({
first_name: signup.first_name,
last_name: signup.last_name,
email: signup.email,
birthday: signup.birthday,
encrypted_password: signup.encrypted_password,
salt: signup.salt
}, {without_protection: true})
You may also want to consider roles:
User.new(params, as: :admin)
The core of the protection is on self.attributes=, which if you use self.send you can trigger a special hidden parameter at the end called guard_protected_attributes as false.
Example:
self.send(:attributes=, hash, false)
This would skip the protection feature completely. It doesn't work on new, but you can simply create the object first then call the same method and save, not too painful I guess.
Related
I am trying to add parents and their children data in the parent and child table. I have existing data in these tables and I am trying to add further data and I don't want the data to be repeated. Below is the code I am using to upload data. The child has parent_id.
parent.rb
has_many :children, dependent: :destroy
def self.import(file)
CSV.foreach(file.path, headers:true) do |row|
parent = Parent.find_or_update_or_create_by(
parent_1_firstname: row['parent_1_firstname'],
parent_1_lastname: row['parent_1_lastname'],
address: row['address'],
address_line_2: row['address_line_2'],
city: row['city'],
province: row['province'],
postal_code: row['postal_code'],
telephone_number: row['telephone_number'],
email: row['email'],
family_situation: row['admin_notes'],
gross_income: row['gross_income'],
created_by_admin: row['created_by_admin'],
status: row['status']
)
parent.children.find_or_create_by(
firstname: row['firstname'],
lastname: row['lastname'],
dateofbirth: row['dateofbirth'],
gender: row['gender']
)
end
end
child.rb
belongs_to :parent
The error I am facing is when I choose the csv file to be uploaded below is the error which I am getting.
undefined method `find_or_update_or_create_by' for #<Class:0x00007f8797be74b0> Did you mean? find_or_create_by
I have added a sample csv below. Please help me figure out the issue.
parent_1_firstname,parent_1_lastname,address,address_line_2,city,province,postal_code,telephone_number,email,admin_notes,gross_income, created_by_admin ,status,firstname,lastname,dateofbirth,gender
Nav,Deo,College Road,,Alliston,BC,N4c 6u9,500 000 0000,nav#prw.com,"HAPPY",13917, TRUE , Approved ,Sami,Kidane,2009-10-10,Male
undefined method `find_or_update_or_create_by' for
Class:0x00007f8797be74b0 Did you mean? find_or_create_by
AFAIK, there is no find_or_update_or_create_by method in Rails. Unless you have defined it as a class method in the Parent model, you can't call that method on a class. I believe you meant to use find_or_create_by. Change
Parent.find_or_update_or_create_by
to
Parent.find_or_create_by
Update:
You cannot call create unless the parent is saved
Ok, so the parent isn't saved which could be due to any validations has failed. Change Parent.find_or_create_by to Parent.find_or_create_by!(as #jvillian stated) which will raise an exception with the validation error message. Fix the error and you are good to go.
To not have to hard-code various nested loops doing find_or_create_by logic, there is a gem called DutyFree that makes imports and exports like this fairly painless. It intelligently analyses the has_many and belongs_to associations on models and based on these relationships identifies how to properly save each imported row across multiple destination tables. Either a create or an update is performed based on if the data already exists or not.
To demonstrate your example from above, I wrote an RSpec test based on the CSV data you provided:
https://github.com/lorint/duty_free/blob/master/spec/models/parent_complex_spec.rb
There is also a simpler example available with just 6 columns:
https://github.com/lorint/duty_free/blob/master/spec/models/parent_simple_spec.rb
One nice thing about this gem is that after configuring the column definitions to do an import, you get export for free because everything works from the same template. For this example here's the template which allows the column names from your CSV to line up perfectly with the database columns:
IMPORT_TEMPLATE = {
uniques: [:firstname, :children_firstname],
required: [],
all: [:firstname, :lastname, :address, :address_line_2, :city, :province, :postal_code,
:telephone_number, :email, :admin_notes, :gross_income, :created_by_admin, :status,
{ children: [:firstname, :lastname, :dateofbirth, :gender] }],
as: {
'parent_1_firstname' => 'Firstname',
'parent_1_lastname' => 'Lastname',
'address' => 'Address',
'address_line_2' => 'Address Line 2',
'city' => 'City',
'province' => 'Province',
'postal_code' => 'Postal Code',
'telephone_number' => 'Telephone Number',
'email' => 'Email',
'admin_notes' => 'Admin Notes',
'gross_income' => 'Gross Income',
'created_by_admin' => 'Created By Admin',
'status' => 'Status',
'firstname' => 'Children Firstname',
'lastname' => 'Children Lastname',
'dateofbirth' => 'Children Dateofbirth',
'gender' => 'Children Gender'
}
}.freeze
With this in your parent.rb, you can call Parent.df_import(your_csv_object) or Parent.df_export, and the gem does the rest.
I need to get all attributes of an object. I know there's a method attributes, but it doesn't return attributes which are nil.
For example:
class User
include Mongoid::Document
field :name
field :email
field :age
end
u = User.new(email: 'foo#bar.com', name: 'foo')
u.save
u.attributes # {'email' => 'foo#bar.com', 'name' => 'foo'}
I need u.attributes to return {'email' => 'foo#bar.com', 'name' => 'foo' 'age' => nil}
There's a method as_json which does what I want, but it's a lot slower. Speed is very important.
I found a quick solution
self.attribute_names.map { |name| [name, self[name]] }.to_h
It does all I want =)
I have a model that has several attributes that are provided at creation. The model also has some additional attributes that are derived from the provided attributes, which I also want to calculate at creation. More problematically, I want to be able to run validations on these derived values (since there are inputs that are valid on their own that lead to invalid derived values).
The problem is that when I do this:
class MyClass < ActiveRecord::Base
attr_accessible :given1, :given2, :derived
before_validation :derivation
validates_uniqueness_of :derived
def derivation
self.derived = self.given1 + self.given2
end
end
MyClass.new(:given1 => aNumber, :given2 => otherNumber)
I always get errors saying I can't add nil to nil. Apparently self.attribute is nil until farther into the validation & creation process.
Obviously I could set my derived values in a later stage, and add a custom validation that works on the given attributes, but that would entail doing the derivation twice, which wouldn't be very DRY.
Is there some other way to get at assigned but not yet validated attributes in the before_validates stage?
Edit: To clarify, I want to call MyClass.new(:given1 => aNumber, :given2 => otherNumber) and have the derived value calculated before the validations check, so that the validations check as if I had called MyClass.new(:given1 => aNumber, :given2 => otherNumber, :derived => aNumber + otherNumber). The problem is that I can't seem to access the passed-in values for :given1 and :given2 in a before_validations method.
I wrote my own snippet of code that looks like this:
class User < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name
validates :email, uniqueness: true
before_validation :derivation
def derivation
self.email = self.first_name + self.last_name
end
end
Running the following yielded no errors:
» u = User.new first_name: "leo", last_name: "correa"
=> #<User:0x007ff62dd8ace0> {
:id => nil,
:first_name => "leo",
:last_name => "correa",
:email => nil,
:created_at => nil,
:updated_at => nil,
}
» u.valid?
User Exists (0.9ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'leocorrea' LIMIT 1
=> true
Running u.save saved the record successfully and upon repeating the User.new and saving that new record it returned with ROLLBACK because email was already used.
In any case, make sure you are assigning whatever variables you are using to the given1, given2 and whatever the result is make sure is not giving you false either because it will cancel the before_validate callback and the record won't save.
I am trying out seeds.rb for the first time, and one of my data models uses encapsulation provided by the money gem.
Relevant gems:
money (3.6.1)
rails (3.0.5)
My model thus far:
app/models/list.rb
class List < ActiveRecord::Base
attr_accessible :alias, :unit, :participating_manufacturer, :quantity
:latest_price_cents, :latest_price_currency, :url
belongs_to :user
composed_of :latest_price,
:class_name => "Money",
:mapping => [%w(latest_price_cents latest_price_cents), %w(latest_price_currency currency_as_string)],
:constructor => Proc.new {
|latest_price_cents, latest_price_currency| Money.new(latest_price_cents ||
0, latest_price_currency || Money.default_currency)
},
:converter => Proc.new {
|value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError,
"Can't convert #{value.class} to Money")
}
end
1) (Addressed successfully)
2) When I get to writing validations, would it be best to write them for the :latest_price attribute or for the :latest_price_cents & :latest_price_currency attributes seperately?
/db/seeds.rb
users = User.create([{ :name => "Foo", :email => "foo#gmail.com",
:password => "foobar", :password_confirmation => "foobar" }])
# etc, will add more users to the array
list = List.create(:user_id => users.first.id, :alias => "Januvia 100mg",
:unit => "tablet", :participating_manufacturer => "Merck",
:quantity => 30, :latest_price_cents => 7500,
:latest_price_currency => "USD", :url =>
"http://www.foobar.com/januvia/100mg-tablets/")
3) Perhaps it is minutiae, but in the seed, should I be assigning values to the virtual :latest_price attribute or to the latest_price_cents and latest_price_currency attributes directly? Is there any way to use faker rather than /db/seeds.rb to perform this task?
I am new to rails and web development.
I can't see your latest_price attribute anywhere, so I'm not sure how to answer your question. Generally, you should validate the attributes entered in the user form. So if a user enters latest_price_cents and latest_price_currency in a form, then they're the ones which need validating.
There's a bug in your seed file. You want to pass in a hash, not an array, when creating a new user; and users should be an array.
users = []
users << User.create!(:name => "Foo",
:email => "foo#gmail.com",
:password => "foobar",)
:password_confirmation => "foobar")
However, if you're considering faker because you want to create some dummy data, take a look at Machinist or Factory Girl. They're designed for creating dummy data, normally for automated tests.
Once you've set up some blueprints, if you want to create dummy data in your seeds file, you can do something like this in seeds.rb:
20.times { List.make } unless Rails.env.production?
I have a Phone model for phone numbers in my application thats built as follows:
t.column :number, :string
t.references :phoneable, :polymorphic => true
I want to restrict the number to be of the format 317.555.5555x234, so I'm creating a form with four boxes (area code, 3 digits, 4 digits, ext):
- form_for #user do |user_form|
-user_form.fields_for :phones do |phone|
= phone.text_field :area_code
= phone.text_field :first_three_digits
etc...
I'm assuming a virtual attribute would be the route to go (a la railscasts ep16), but not sure how to assemble the "number" from the 4 separate text_fields.
I think I would have to do something like this:
def full_number=(phone)
self.number = area_code+"."+first_three_digits+"."+second_four_digits+"."+extension
end
But I'm unsure of how to approach this in assembling the number from form inputs. Any thoughts?
I normally do this as a before_save:
before_save :update_phone_number
def update_phone_number
self.phone_number = [area_code, first_three_digits, second_four_digits, extension].reject(&:blank?).join('.')
end
First I would have some validations:
validates_presence_of :area_code, :first_three_digits, :second_four_digits
validates_format_of :area_code, :with => /\d{3}/
validates_format_of :first_three_digits, :with => /\d{3}/
validates_format_of :second_four_digits, :with => /\d{4}/
validates_format_of :extension, :with => /\d{0,6}/, :allow_blank => true
This is just to make sure that you get valid data in your phone number and your before save doesn't throw any errors. I also assumed that you would allow the extension to be blank, but is easily changed.
EDIT: you will want to have attr_accessors for the different segments of the phone number:
attr_accessor :area_code, :first_three_digits, :second_four_digits, :extension