protection level for model attributes in rails - ruby-on-rails

Suppose the following situation
class User < ActiveRecord::Base
private
def password= p
self[:password] = p
end
def password
self[:password]
end
end
If anyone with access to the Rails console can do:
Loading development environment (Rails 4.0.0)
2.0.0p247 :001 > User
=> User(id: integer, name:string, password:string)
2.0.0p247 :002 > u = User.find(1)
=> #<User id: 1, name: "Jack", password: "da6c253ffe0975ca1ddd92865ff3d5f0">
2.0.0p247 :003 > u.password = "123"
NoMethodError: private method 'password' called for #<User:0xa9145b0>
2.0.0p247 :004 > u[:password] = "123"
=> "123"
2.0.0p247 :005 > u
=> #<User id: 1, name: "Jack", password: "123">
2.0.0p247 :005 > u.save
=> true
Why does this happen? How can I encapsulate critical fields?

I am guessing that password is attr_accessible in the model. When a field is attr_accessible, Rails automatically lets you read and write to the field. You have a private password method that overwrites the Rails password and password= methods, but you did not overwrite the [] and []= methods as well. You can either overwrite the [] and []= methods or make it so password is not attr_accessible.
Here is a code example of how to overwrite the [] method:
class User < ActiveRecord::Base
def [](word)
puts "I am the master of: #{word}"
end
def []=(key, value)
puts "Fluffy monsters"
end
end
With this revised code, here is what the [] method will return:
>> u[:password] = "123"
=> nil
# prints "Fluffy monsters" in the console
>> u[:password]
=> nil
# prints "I am the master of: password" in the console

Related

ArgumentError : Missing Keyword when Init a Class

I have a simple pattern result class to init on controller, everything works well on ruby 2.3.x <= 2.7.x, I try on ruby 3.0.1 with Rails 6.1.4.
My project structure looks like :
app/
..controllers
....api
......v1
.........files_controllers.rb
..jobs
..mailers
..serializers
..services
....v1
......list_result.rb
..value_objects
A simple pattern result class I put on services directory
module V1
class ListResult
attr_reader :status,
:status_message,
:data,
:meta
SUCCESS = 1
def initialize(status_message:, data:, meta:)
#status = SUCCESS
#status_message = status_message
#data = data
#meta = meta
end
def success?
#status == SUCCESS
end
end
end
And try to in on controller looks like :
V1::ListResult.new(status_message: "", data: [], meta: {})
I'm getting this error :
ArgumentError: wrong number of arguments (given 1, expected 0; required keywords: status_message, data, meta)
from /app/services/v1/list_result.rb:10:in `initialize
but it's works on rails console
3.0.1 :001 > V1::ListResult.new(status_message: "", data: [], meta: {})
=> #<V1::ListResult:0x00007f8d3dbaa730 #data=[], #meta={}, #status=1, #status_message="">
irb console
3.0.1 :001 > class MyClass
3.0.1 :002 > def initialize(status_message:, data:, meta:)
3.0.1 :003 > puts "#{status_message} #{data} #{meta}"
3.0.1 :004 > end
3.0.1 :005 > end
3.0.1 :006 > MyClass.new(status_message: "", data: [], meta: {})
[] {}
I've read about Separation of positional and keyword arguments in Ruby 3.0, but I confused why it's work on rails console and irb console only?

Rails 5 field _type not generated in mongoid

I have class & subclass with one document looks like :
class Core::User
include Mongoid::Document
include Mongoid::Timestamps
store_in collection: 'users'
end
class Core::Moderator < Core::User
end
I tried to add a user from console
2.4.2 :002 > user = Core::User.new(email: 'email#domain.com', name: 'new user')
=> #<Core::User _id: BSON::ObjectId('5a015465fe37a86430b1e0ff'), created_at: nil, email: "email#domain.com", name: "new_user", updated_at: nil>
2.4.2 :003 > user.save
=> true
2.4.2 :004 > user._type
NoMethodError: undefined method `_type' for #<Core::User:0x0000000003e77ea0>
from (irb):4
And then add new moderator :
2.4.2 :005 > moderator = Core::Moderator.new(email: 'email2#domail.com', name: 'new moderator')
#<Core::Moderator _id: BSON::ObjectId('5a015600fe37a86430b1e100'), created_at: nil, email: "email2#domain.com", name: "new moderator", updated_at: nil>
2.4.2 :006 > moderator.save
=> true
2.4.2 :007 > moderator._type
=> "Core::Moderator"
Next add new user again :
2.4.2 :008 > user = Core::User.new(email: 'email3#domain.com', name: 'new user 2')
=> #<Core::User _id: BSON::ObjectId('5a015704fe37a86430b1e101'), created_at: nil, email: "email3#domain.com", name: "new user 2", updated_at: nil>
2.4.2 :009 > user.save
=> true
2.4.2 :010 > user._type
=> "Core::User"
Why I should create subclass first to get field _type on parent class? Every I start new console and create new user (Core::User) the field _type not generated.
I user Ruby 2.4.2, Rails 5.1.4, Mongoid 6.2.1
In order for inheritance to work as expected in Mongoid you need to set preload_models: true. Otherwise the model cannot know that it has subclasses.
# config/mongoid.yml
development:
# ...
options:
# ...
# Preload all models in development, needed when models use
# inheritance. (default: false)
preload_models: true

Big Decimal Stored as 60.00, Returns as 0.0?

I am customizing a Spree 2.3 application in Rails 4. When I save a price.amount, it appears to save correctly in the database, but when I retrieve it, it is wrapped in the BigDecimal class and returns 0.
How can I store, or retrieve, the correct value?
# in the form
<%= number_field_tag :amount %>
# controller action
#variant.price = params[:amount]
# appears in PostgresQL database as type 'numeric'
id: 35, amount: 60.00
# retrieved in Rails console as BigDecimal class instance
# looks like the wrong amount
2.1.1 :001 > Spree::Price.find_by_id(35).amount
=> #<BigDecimal:105806d20,'0.0',9(27)>
# converts to 0.0
2.1.1 :002 > Spree::Price.find_by_id(35).amount.to_f
=> 0.0
# testing data retrieval in the console
2.1.1 :003 > ActiveRecord::Base.connection.execute("SELECT * FROM spree_prices WHERE id=35").first
=> {"id"=>"35", "variant_id"=>"35", "amount"=>"60.00", "currency"=>"USD", "deleted_at"=>nil}
2.1.1 :004 > Spree::Price.find(35)
=> #<Spree::Price id: 35, variant_id: 35, amount: #<BigDecimal:109ec4a28,'0.0',9(27)>, currency: "USD", deleted_at: nil>
The problem was in a typo in the price model decorator. The pure PostgresQL doesn't trigger the callback, but using the 'find' method does. This accounts for the seemingly impossible results above.
#original price_decorator.rb
after_initialize :set_defaults
def set_defaults
self.amount = 0.0
end
#corrected price_decorator.rb
after_initialize :set_defaults
def set_defaults
self.amount ||= 0.0
end

Rails new objects are nil

I'm playing with rails again and found this behavior, when i create a new instance of a Post model with some attributes it tells me that all attributes are nil, why it is happening?
Loading development environment (Rails 4.0.0)
2.0.0-p451 :001 > a = Post.new(title: "Rails", content: "Rails Post")
=> #<Post id: nil, title: nil, content: nil, author: nil, rating: nil, created_at: nil, updated_at: nil>
2.0.0-p451 :002 > a.title
=> "Rails"
2.0.0-p451 :004 > a.content
=> "Rails Post"
2.0.0-p451 :005 > a.inspect
=> "#<Post id: nil, title: nil, content: nil, author: nil, rating: nil, created_at: nil, updated_at: nil>"
2.0.0-p451 :006 > a.errors.messages
=> {}
2.0.0-p451 :007 > a.valid?
=> true
class Post < ActiveRecord::Base
attr_accessor :title, :content, :author, :rating
end
You are defining attr_accessor for all your properties, which is a shortcut for defining getters and setters for an instance variable of the same name like so:
def content
#content
end
def content=(new_content)
#content = new_content
end
Rails will also auto-generate you methods with these names, for every database field that your model has. These methods will conflict with each other.
When you call post.content = 'foo', instead of calling the Rails-generated method that will internally set your model's content attribute to 'foo', you're calling the attr_accessor-defined method which will set the instance variable #content to 'foo'.
The output of inspect is iterating over the Rails-defined model attributes, not the instance variables.
Did you actually mean to declare these attributes as attr_accessible instead of attr_accessor?

conditions case not working correctly

i use acts-as-taggable-on for tagging.
apartments_controller
def index
if params[:tag]
#apartments = Apartment.tagged_with(params[:tag])
else
#apartments = Apartment.all
end
end
routes
resources :apartments do
#...
collection do
get ':tag', to: 'apartments#index', as: :tag
end
#...
I get nice urls by example /apartments/tag1 etc.
I want to show custom content based on the tag name in the apartments index template.
Apartment's index view:
- #appartments.each do |tags|
- case tags.tag_list
- when "tag1"
%p tag1 content
- when "tag2"
%p tag2 content
- else
%p default content
When i go to url apartments/tag1 the text "default content" is show and not "tag1 content".
What am I doing wrong?
There several notes about your code:
Keep your logic away from views. I.e. extract code into helper methods.
Do not use case on Enumerable, in your case it seems like an array. Use include? to check whether element is present inside an array:
1.9.3p194 :001 > a= [:a, :b, :c]
=> [:a, :b, :c]
1.9.3p194 :002 > case a
1.9.3p194 :003?> when :a
1.9.3p194 :004?> p '1'
1.9.3p194 :005?> when :b
1.9.3p194 :006?> p '2'
1.9.3p194 :007?> when :c
1.9.3p194 :008?> p '3'
1.9.3p194 :009?> when :d
1.9.3p194 :010?> p 'Never'
1.9.3p194 :011?> end
=> nil
1.9.3p194 :012 > a.include?(:c)
=> true

Resources