Accessing attributes from rails setter - ruby-on-rails-3

I am kind of stuck trying to access (read) an attribute from a custom setter in Rails:
def password=(new_password)
puts self.firstname # returns nil
# Do other checks here before setting a hashed SCrypt password
end
The firstname is defined on the (user) object, however calling the firstname will not yield the value set. I need to read various attributes from the custom setter to do password checks up against firstname, lastname and email address.

Do you have the corresponding getter method?
def password
#password
end
Or if it's a really simple getter/setter pair just
:attr_accessor password
Update on your comment: so you're wanting to do something other than just set the attribute in the setter?
So obviously I have to advise against that. However you can just do something like:
def password=(new_password)
some_method(new_password)
#password = new_password
end
This will pass the new_password method out of the setter, allowing you to do other things with it, before setting the instance variable to the new value.
If you are trying to access the firstname attribute within the password setter, as in your example, you can do it this way:
def password=(new_password)
some_method(firstname)
#password = new_password
end
You don't use self to call the getter method.

Ok seems to be an issue with my app somewhere. I just did a minimal test application that works fine:
class User < ActiveRecord::Base
def password
password ||= SCrypt::Password.new(password_hash)
end
def password=(new_password)
logger.info 'DEBUG: self.lastname: ' + self.lastname.to_s
self.password_hash = SCrypt::Password.create(new_password, :key_len => 512, :salt_size => 32)
end
end
In console it works fine:
Loading development environment (Rails 3.2.12)
2.0.0-p0 :001 > u = User.new
=> #<User id: nil, firstname: nil, lastname: nil, password_hash: nil, created_at: nil, updated_at: nil>
2.0.0-p0 :002 > u.lastname = "Some Lastname"
=> "Some Lastname"
2.0.0-p0 :003 > u.password = 'test'
DEBUG: self.lastname: Some Lastname
=> "test"
So it seems to be some issue related to a gem / something / somewhere in the application and not a Rails specific problem. Apologies to the list.

The order of the params matters here based on what I have observed.
⚠️ Notice the order of :firstname and :password when passing.
If you're using params.require(:user).permit(:firstname, :password) and then passing it to your setters via .new or =setter methods. Then you can always access self.firstname in your password= setter method because you firstname got set first and it's available to access for next setters.
However, if you have params.require(:user).permit(:password, :firstname) then your password setter method cannot access self.firstname.

Related

Assign current user in session for tests

I have found similar answers to this here and here, but it does not seem to be working.
I am having trouble assigning the current_user to the session when testing. My tests are set up like this
setup do
%User{
id: 123456,
name: "MyName",
email: "abc#gmail.com",
password_hash: Comeonin.Bcrypt.hashpwsalt("password")
} |> Repo.insert
{:ok, user: Repo.get(User, 123456) }
end
and my test is
test "renders index.html on /coping-strategy", %{user: user} do
conn = conn
|> assign(:current_user, user)
|> get(coping_strategy_path(conn, :index))
assert html_response(conn, 200) =~ "Coping strategies"
end
When I inspect the conn here, current_user has been correctly assigned to user. However, when I inspect the conn in my controller during testing, current_user is always nil. I tried using build_conn as well, but it is still nil.
Here is the controller:
def index(conn, _params) do
user_id = conn.assigns.current_user.id
coping_strategies = Post
|> Post.get_coping_strategies(user_id)
|> Repo.all
render conn, "index.html", coping_strategies: coping_strategies
end
If I try to assign something other than current_user, then it shows up when I inspect the conn in the controller. So if I added in |> assign(:stuff, "test") in the test, then I have stuff: "test" in assigns in the conn. If I change current_user to current_user2 in the test and in the controller, it works because current_user2 updates and is passed along as I expect it to be.
Is there a reason I cannot assign the current user like this and why it is always nil? Are there any steps I am missing to do this for current_user in particular?
Edit
Here's where current_user is being set in the auth plug
def call(conn, repo) do
user_id = get_session(conn, :user_id)
user = user_id && repo.get(Healthlocker.User, user_id)
assign(conn, :current_user, user)
end
Since you're directly setting :current_user in assigns in the tests, you need to skip fetching the user from the session's :user_id if assigns contains :current_user in the auth plug:
def call(conn, repo) do
if conn.assigns[:current_user] do
conn
else
user_id = get_session(conn, :user_id)
user = user_id && repo.get(Healthlocker.User, user_id)
assign(conn, :current_user, user)
end
end

Rails 3 polymorphic_path - how to change the default route_key

I got a config with Cart and CartItem (belongs_to :cart) models.
What I want to do is to call polymorphic_path([#cart, #cart_item]) so that it uses cart_item_path, instead of cart_cart_item_path.
I know I can change the url generated by the route to /carts/:id/items/:id, but that's not what I'm interested in. Also, renaming CartItem to Item is not an option. I just want to use cart_item_path method throughout the app.
Thanks in advance for any tip on that!
Just to make my point clear:
>> app.polymorphic_path([cart, cart_item])
NoMethodError: undefined method `cart_cart_item_path' for #<ActionDispatch::Integration::Session:0x007fb543e19858>
So, to repeat my question, what can I do in order for polymorphic_path([cart,cart.item]) to look for cart_item_path and not cart_cart_item_path?
After going all the way down the call stack, I came up with this:
module Cart
class Cart < ActiveRecord::Base
end
class Item < ActiveRecord::Base
self.table_name = 'cart_items'
end
def self.use_relative_model_naming?
true
end
# use_relative_model_naming? for rails 3.1
def self._railtie
true
end
end
The relevant Rails code is ActiveModel::Naming#model_name and ActiveModel::Name#initialize.
Now I finally get:
>> cart.class
=> Cart::Cart(id: integer, created_at: datetime, updated_at: datetime)
>> cart_item.class
=> Cart::Item(id: integer, created_at: datetime, updated_at: datetime)
>> app.polymorphic_path([cart, cart_item])
=> "/carts/3/items/1"
>> app.send(:build_named_route_call, [cart, cart_item], :singular)
=> "cart_item_url"
I think the same could work for Cart instead of Cart::Cart, with use_relative_model_naming? on the Cart class level.
You can declare the resources like this in your routes file.
resources :carts do
resources :cart_items, :as => 'items'
end
Refer to this section of the rails guide

Where to set session default value?

guys!
Prior to asking i should mention, that i`m working without ActiveRecord or any self-hosted-database. So thats why i have to store some values in the session.
From the very begining i desided to set session value of the users city in the layout. - i supposed it would be loaded before anything else. So i`ve done something like this:
<% session[:city] ||= {:name => 'City-Name', :lat => '40', :lng => '40'}%>
But when i`m loading directly to inner page it occurs that session[:city is nil *(
How should i set the session properely, so that it wouldn`t be nil???
I had similar needs in one of the applications I worked on. It needed the users data to be loaded on sign-in and stored in the session. So, wrote a module called session_helpers.rb with the following:
module SessionHelpers
def get_value(key)
session[key.to_sym]
end
protected
def store_data(*objects)
objects.each do |object|
if object.is_a?(Hash)
object.each do |key, value|
session[key.to_sym] = value
end
end
end
end
def remove_data(*objects)
objects.each do |object|
if object.is_a?(String)
key = to_id(object)
else
key = to_id(object.class.name)
end
session[key] = nil
end
end
def update_data(key, value)
session[key.to_sym] = value
end
private
def to_id(name)
"#{name.parameterize('_').foreign_key}".to_sym
end
end
You can make any or all the methods available to views as well:
# application_controller.rb
helper_method :get_value
From the model I would retrieve a hash of the data that needs to be put up in the session about the user:
def common_data
#data = Hash.new
#data.merge!( { 'news' => self.news.count } )
...
#data
end
As I wanted to do this after sign-in I overrode the devise method to do this:
def after_sign_in_path_for(resource_or_scope)
store_data( '_count', current_user.common_data )
dashboard_path
end
This way I was able to load important data about the user on sign-in and store it in the session and retrieve whenever I wanted. Hope this helps.

Retrieving an ActiveRecord length validation returns nil

I want to retrieve the maxmimum lenght validation of a ActiveRecord field in one of my views.
The following works fine in rails console and returns the correct value :
irb(main):046:0> micropost = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
irb(main):047:0> micropost._validators[:content][1].options[:maximum].to_s
=> "140"
However, when I use the same code in my controller it returns nil :
class PagesController < ApplicationController
def home
#title = "Home"
if signed_in?
#micropost = Micropost.new
#feed_items = current_user.feed.paginate(:page => params[:page])
#content_max = #micropost._validators[:content][1].options[:maximum].to_s
end
end
...
end
I also tried to include a method in my ApplicationHelper, which also returns nil ;-(
def content_max
Micropost._validators[:content][1].options[:maximum].to_s
end
What am I doing wrong?
The _validators array might not be in the same order whether you're in the console or in a web request.
Micropost._validators[:content].find {|v| v.class == ActiveModel::Validations::LengthValidator} .options[:maximum].to_s
should do what you want.
IMHO, a better solution would be to store the length in a constant (I somehow doubt the _validators array is part of the official API) :
class Micropost < ActiveRecord::Base
MAX_CONTENT_LENGTH = 140
validates :content, :length => {:maximum => MAX_CONTENT_LENGTH}
# Rest of the code...
end
and get the length with :
Micropost::MAX_CONTENT_LENGTH

Why doesn't this test in the Diaspora app fail?

From http://github.com/diaspora/diaspora/blob/master/spec/models/profile_spec.rb
describe Profile do
before do
#person = Factory.build(:person)
end
describe 'requirements' do
it "should include a first name" do
#person.profile = Factory.build(:profile,:first_name => nil)
#person.profile.valid?.should be false
#person.profile.first_name = "Bob"
#person.profile.valid?.should be true
end
end
end
But in http://github.com/diaspora/diaspora/blob/master/app/models/profile.rb is validated the presense of both, the first and last name like so validates_presence_of :first_name, :last_name
Why does the above test pass even though a last name isn't specified?
last_name is actually specified. The profile is create using the Factory.build, which returns the predefined mock of :profile, which is
Factory.define :profile do |p|
p.first_name "Robert"
p.last_name "Grimm"
end
I suspect the Factory.build(:profile, ...) call creates a profile model with a default first_name and last_name set, unless specified otherwise (by the :first_name => nil in this example).
However that's just an educated guess which I am inferring from the code above and what I see here.