分类目录归档:ruby学习

The Safe Navigation Operator (&.) in Ruby

The most interesting addition to Ruby 2.3.0 is the Safe Navigation Operator(&.). A similar operator has been present in C# and Groovy for a long time with a slightly different syntax – ?.. So what does it do?

Scenario

Imagine you have an account that has an owner and you want to get theowner’s address. If you want to be safe and not risk a Nil error you would write something like the following:

if account && account.owner && account.owner.address
...
end

This is really verbose and annoying to type. ActiveSupport includes the trymethod which has a similar behaviour (but with few key differences that will be discussed later):

if account.try(:owner).try(:address)
...
end

It accomplishes the same thing – it either returns the address or nil if some value along the chain is nil. The first example may also return false if for example the owner is set to false.

Using &.

We can rewrite the previous example using the safe navigation operator:

account&.owner&.address

The syntax is a bit awkward but I guess we will have to deal with it because it does make the code more compact.

More examples

Let’s compare all three approaches in more detail.

account = Account.new(owner: nil) # account without an owner

account.owner.address
# => NoMethodError: undefined method `address' for nil:NilClass

account && account.owner && account.owner.address
# => nil

account.try(:owner).try(:address)
# => nil

account&.owner&.address
# => nil

No surprises so far. What if owner is false (unlikely but not impossible in the exciting world of shitty code)?

account = Account.new(owner: false)

account.owner.address
# => NoMethodError: undefined method `address' for false:FalseClass `

account && account.owner && account.owner.address
# => false

account.try(:owner).try(:address)
# => nil

account&.owner&.address
# => undefined method `address' for false:FalseClass`

Here comes the first surprise – the &. syntax only skips nil but recognizesfalse! It is not exactly equivalent to the s1 && s1.s2 && s1.s2.s3 syntax.

What if the owner is present but doesn’t respond to address?

account = Account.new(owner: Object.new)

account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>

account && account.owner && account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`

account.try(:owner).try(:address)
# => nil

account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`

Oops, the try method doesn’t check if the receiver responds to the given symbol. This is why it’s always better to use the stricter version of trytry!:

account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`

Pitfalls

As Joeri Samson pointed out in the comments, this section is actually wrong – I mistakenly used ?. instead of &.. But I still think that the last example is confusing and nil&.nil? should return true.

Be careful when using the &. operator and checking for nil values. Consider the following example:

nil.nil?
# => true

nil?.nil?
# => false

nil&.nil?
# => nil

Array#dig and Hash#dig

The #dig method is, in my opinion, the most useful feature in this version. No longer do we have to write abominations like the following:

address = params[:account].try(:[], :owner).try(:[], :address)

# or

address = params[:account].fetch(:owner) .fetch(:address)

You can now simply use Hash#dig and accomplish the same thing:

address = params.dig(:account, :owner, :address)

Final words

I really dislike dealing with nil values in dynamic languages  and think the addition of the safe operator and the digmethods is really neat. Note that Ruby 2.3.0 is still not released and some things might change in the final version.

 

single-table inheritance单表继承

 

type 字段在 Rails 中默认使用来做 STI(single-table inheritance),
当 type 作为普通字段来使用时,可以把SIT的列设置成别的列名(比如不存在的某个列)。

文档在这里
http://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-inheritance_column,
使用下面的方法就是设置 STI 使用的列名,默认是‘type',既然不想使用 STI了,可以设置一个不太可能和以后的列名冲突的名字,比如使用 ’_disable‘。

比如可以在 Model 中设置为
self.inheritance_column = '_disable'

Defines the name of the table column which will store the class name on single-table inheritance situations.

The default inheritance column name is type, which means it’s a reserved word inside Active Record. To be able to use single-table inheritance with another column name, or to use the column type in your own model for something else, you can set inheritance_column:

self.inheritance_column = 'zoink'

关于single table inheritance, 可以查看下面的文档
http://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html




以下是建表语句
class CreateSubjects < ActiveRecord::Migration[5.0]
  def change
    create_table :subjects do |t| 
      t.string :title, limit: 40
      t.string :sub_title, limit: 40
      t.string :categories, limit: 30
      t.string :description, limit: 500 
      t.integer :status, limit: 1
      t.integer :type, limit: 1
      t.integer :use_default_image, limit: 1
      t.integer :has_ranking_list, limit: 1

      t.timestamps
    end
  end 
end

 

ducument.ready不生效的问题 ruby on rails

rails web app页面之间的跳转的时候使用ducument.ready只有在再次加载的时候才生效, 因为rails用了turbolinks,

https://github.com/turbolinks/turbolinks/blob/master/README.md#running-javascript-when-a-page-loads

Running JavaScript When a Page Loads

You may be used to installing JavaScript behavior in response to the window.onloadDOMContentLoaded, or jQuery readyevents. With Turbolinks, these events will fire only in response to the initial page load—not after any subsequent page changes.

In many cases, you can simply adjust your code to listen for the turbolinks:load event, which fires once on the initial page load and again after every Turbolinks visit.

document.addEventListener("turbolinks:load", function() {
  // ...
})

When possible, avoid using the turbolinks:load event to add event listeners directly to elements on the page body. Instead, consider using event delegation to register event listeners once on document or window.

  1. $(document).ready 依赖于 DOMContentLoaded 事件
  2. Turbolinks 接管页面后换页不会产生 DOMContentLoaded,所以换页之后 $(document).ready 无效
  3. Turbolinks 提供了 page:change 取代 $(document).ready
  4. 现在第一次加载也会触发 page:change 了,所以 page:change 可以替代 $(document).ready

要注意 turbolinks 会缓存访问过的页面,缓存 restore 的时候也会触发 page:chang,这样的代码在用户后退的时候会重复绑定:

$(document).on 'page:change', ->
  $('body').on 'click', ->
    console.log('hit')

page:change 和 $().ready 逻辑一样,区别是 turbolinks restore 的时候页面带着之前的状态,而传统页面后退的时候是干净的。

如果要用 turbolinks,最好就用 page:change,并且要写可以反复执行不冲突的代码。

现在用 page:change 很少,换成这样写:

$(document).on 'click', 'body', ->
  console.log('hit')

还有个 page:load,考虑到 restore 的问题,page:load 才是对应 $().ready

 

document.addEventListener("turbolinks:load",
  function() {
    if ($('#component_component_type').attr("data-value") == "display_item") {
      $('.display-item-select').show();
      $('.grid-select').hide();
    } else if($('#component_component_type').attr("data-value") == "grid") {
      $('.grid-select').show();
      $('.display-item-select').hide();
    } else {
      $('.grid-select').hide();
      $('.display-item-select').hide();
    }   
})


$(document).on('change', '#component_component_type_id', function(e) {
    if ($('#component_component_type_id').find("option:selected").text() == "display_item") {
      $('.display-item-select').show();
      $('.grid-select').hide();
    } else if($('#component_component_type_id').find("option:selected").text() == "grid") {
      $('.grid-select').show();
      $('.display-item-select').hide();
    } else {
      $('.grid-select').hide();
      $('.display-item-select').hide();
    }   
});

 

 

使用text存储hash类型的数据 Use text filed to store the hash map

 

在component表里用text类型的字段存储hash数据

(1)新建字段 ,这是migration的内容

class AddHintsToComponents < ActiveRecord::Migration[5.0]
  def change
    add_column :components, :hints, :text
  end 
end

(2)controller

  def update
    if @component.update!(component_params)
      redirect_to @component, notice: 'Component was successfully updated.'
    else
      render :error
    end 
  end 
    def component_params
      params.require(:component).permit(:release_id, 
                                        :remark, component_ids: [], hints: [:left, :mid, :right])
    end

(3)model

class Component < ApplicationRecord
  serialize :hints
       
  def after_initialize
      self.hints ||= {}
  end

(4)_component.html.erb

  <td>
    left:<%= component.hints["left"] %> |
    mid:<%= component.hints["mid"] %> |
    right:<%= component.hints["right"] %>
  </td

(5)_form.html.erb

  <%= f.fields_for :hints do |h| %>
    <div class="form-group">
      <%= f.label 'hints left', class: 'col-sm-2 control-label' %>
      <div class="col-sm-4">
        <%= h.text_field :left, class: 'form-control',
          value: (@component.hints && @component.hints.key?("left") ? @component.hints["left"] : "") %>
      </div>
    </div>

    <div class="form-group">
      <%= f.label 'hints mid', class: 'col-sm-2 control-label' %>
      <div class="col-sm-4">
        <%= h.text_field :mid, class: 'form-control',
          value: (@component.hints && @component.hints.key?("mid") ? @component.hints["mid"] : "") %>
      </div>
    </div>

    <div class="form-group">
      <%= f.label 'hints right', class: 'col-sm-2 control-label' %>
      <div class="col-sm-4">
        <%= h.text_field :right, class: 'form-control',
          value: (@component.hints && @component.hints.key?("right") ? @component.hints["right"] : "") %>
      </div>
    </div>
  <% end %>

参考资料

http://vladexologija.blogspot.com/2012/12/rails-attribute-serialization_18.html

http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters

index and polymorphic

http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

class CreateStars < ActiveRecord::Migration
  def self.up
    create_table :cms_tv_stars do |t| 
      t.string :username
      t.string :image
      t.integer :person_id

      t.timestamps
    end 

    change_table :cms_tv_stars do |t| 
      t.index :person_id, uniq: true
    end 
  end 

  def self.down
    drop_table :cms_tv_stars
  end 
end

 

class CreateSubchannelItems < ActiveRecord::Migration
  def self.up
    create_table :tv_subchannel_items do |t| 
      t.string :title
      t.string :subtitle
      t.string :version
      t.string :image
      t.references :subchannel
      t.references :showable, polymorphic: true
      t.integer :state, limit: 1, default: 0
      t.integer :position, default: 1

      t.timestamps
    end 

    change_table :tv_subchannel_items do |t| 
      t.index [:showable_type, :showable_id], name: :subchannel_items_showable_index
      t.index [:subchannel_id, :state, :version, :position], name: :subchannel_items_sort_index
    end 
  end 

  def self.down
    drop_table :tv_subchannel_items
  end 
end

http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

If you have an instance of the Picture model, you can get to its parent via @picture.imageable.

To make this work, you need to declare both a foreign key column and a type column in the model that declares the polymorphic interface:

class CreatePictures < ActiveRecord::Migration
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps null: false
    end
 
    add_index :pictures, :imageable_id
  end
end

This migration can be simplified by using the t.references form:

class CreatePictures < ActiveRecord::Migration
  def change
    create_table :pictures do |t|
      t.string :name
      t.references :imageable, polymorphic: true, index: true
      t.timestamps null: false
    end
  end
end

 

Let’s check the index in the database

$ bundle exec rails db -p
mysql> show index from tv_subchannel_items;
+---------------------+------------+---------------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table               | Non_unique | Key_name                        | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------------------+------------+---------------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| tv_subchannel_items |          0 | PRIMARY                         |            1 | id            | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
| tv_subchannel_items |          1 | subchannel_items_showable_index |            1 | showable_type | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |               |
| tv_subchannel_items |          1 | subchannel_items_showable_index |            2 | showable_id   | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |               |
| tv_subchannel_items |          1 | subchannel_items_sort_index     |            1 | subchannel_id | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |               |
| tv_subchannel_items |          1 | subchannel_items_sort_index     |            2 | state         | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |               |
| tv_subchannel_items |          1 | subchannel_items_sort_index     |            3 | version       | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |               |
| tv_subchannel_items |          1 | subchannel_items_sort_index     |            4 | position      | A         |           0 |     NULL | NULL   | YES  | BTREE      |         |               |
+---------------------+------------+---------------------------------+--------------+---------------+-----------+-------------+----------+-----

 

Public and Private Interfaces in ruby

Your latest client is a bank, and they’ve tasked you with requiring customers to enter their password in order to make withdrawals.

Currently, this is what they’ve got:

class Customer
  attr_reader :funds

  def initialize(funds, password)
    @funds = funds
    @password = password
  end

  def remove_funds(amount)
    @funds -= amount
  end
end

Let’s break that apart. You can paste that whole class into irb to follow along.

When a customer is initialized, it receives a specified amount of funds and a password is set.

diego = Customer.new(500, "udacious")
# => #<Customer:0x007fcdb48ca5a8 @funds=500 @password="udacious">

Thanks to the attr_reader, you can see the value of his current funds.

diego.funds
# => 500

And the remove_funds method allows funds to be removed from the customer’s account.

Checking on the funds again confirms this.

diego.remove_funds(50)
# => 450
diego.funds
# => 450

These methods, funds and remove_funds, are part of the Customer class’ API, or application programming interface.

An API is, according to Wikipedia, “a set of routines, protocols, and tools for building software applications”.

Well, that’s vague.

“API” is a popular term in recent years, but many people use it without quite understanding what it means. Think of methods like remove_funds as your way of interfacing with the Customer class. These methods are the keys to accessing information about a particular customer.

There isn’t currently a way to access the @password instance variable.

It could be said that the customer’s password can’t be accessed by the customer’s public API.

In this situation, that’s a good thing! You don’t want information like a password to be publicly available to other objects.

Let’s implement a method called withdraw_securely, which takes two arguments, amount andpassword.

If the password entered matches the customer’s password, go ahead and remove the funds. Otherwise, nothing happens.

class Customer
  attr_reader :funds

  def initialize(funds, password)
    @password = password
    @funds = funds
  end

  def remove_funds(amount)
    @funds -= amount
  end

  def withdraw_securely(amount, password)
    if password == @password
      remove_funds(amount)
    end
  end
end

Play around with this in irb to see it in action.

diego.withdraw_securely(50, "udacious")
# => 400
diego.withdraw_securely(100, "wrong password")
# => nil
diego.funds
# => 400

✨Hooray. Calling withdraw_securely using the correct password decreases the total funds by calling remove_funds,

while using the incorrect password does nothing.

There’s one issue here, can you spot it?

diego.remove_funds(75)
# => 325
diego.funds
# => 325

Malicious users can still withdraw funds directly using the remove_funds method!

With this current setup, both remove_funds and withdraw_securely are part of a customer’s public API.

In reality, remove_funds needs to somehow be tucked away so that it isn’t accessible via the public API.

This can be done by making it a private method.

Private methods can be invoked only from within a class. If remove_funds were a private method,

that would mean it can only be accessed by another method, like withdraw_securely. To do this, you can use the private keyword.

class Customer
  attr_reader :funds

  public

  def initialize(funds, password)
    @password = password
    @funds = funds
  end

  def withdraw_securely(amount, password)
    if password == @password
      remove_funds(amount)
    end
  end

  private

  def remove_funds(amount)
    @funds -= amount
  end
end

Here, anything after the private keyword will be treated as a private method. Test it out in irb and see it in action!

diego.withdraw_securely(75, "udacious")
# => 250

Okay, withdraw_securely still works as expected…

diego.remove_funds(100)
NoMethodError: private method 'remove_funds' called for
#<Customer:0x007fcdb496ae40 @funds=400, @password="udacious">

But now remove_funds throws an error! A NoMethodError in particular. If you read through this error,

you’ll see that it does exactly what you intended: it will not let you call the private method,remove_funds.

Using private methods allows you to reuse logic in your code without having to worry about exposing it to other classes.

As a guiding principle, it’s not a bad idea to assume a method should be private until you’ve been given a good reason to make it a part of an object’s public API.

Going Public

Technically, there’s also a public keyword. It works just like the private keyword, and it’s used to indicate that methods should be publicly available.

The public and private keywords (there’s also protected, but we’ll get to that later) can only be used once inside each class, and methods are by default public.

class Customer
  attr_reader :funds

  public

  # public methods...

  private

  # private methods...
end

While this would technically work just fine, using the public keyword tends to be redundant. You’ll hardly ever see it in code anywhere.

A disclaimer about Private Methods

Guilty confession: just because a method is private doesn’t mean it can’t ever be called.

diego.send(:remove_funds, 65)
# => 185

There’s a little workaround using the send method, which sends the method of the name you specify (in this case, :remove_funds) to be called by the object.

What does this mean for you? Well, if security is actually a primary concern for you, creating a private method alone isn’t enough of a solution.

But the benefits of tucking away private methods remain. Indicating that a method is private lets other developers know that a method isn’t intended to be part of an object’s public API, and discourages them from using it.

Encapsulation and Requiring Files

By encapsulating all the logic for an object, whether it’s a Dog or a User or an IceCreamShop, you are able to keep all of the logic and responsibilities of your object within its own scope. This lets you avoid having to worry about other objects using methods that they shouldn’t have access to.

Let’s take one last look at how your Dog methods took advantage of encapsulation. Let’s say you also had some methods for your pet parrot in there as well. Here’s what your code might have looked like:

 

def drink_water_from_bowl(dog)
  # code for method
end

def wants_to_play?(dog)
  # code for method
end

def fly_away(parrot)
  # code for method
end

def wag_tail(dog)
  # code for method
end

And here’s what it would look like using OOP:

class Dog
  def drink_water_from_bowl
    # code for method
  end

  def wants_to_play?
    # code for method
  end

  def wag_tail
    # code for method
  end
end

class Parrot
  def fly_away
    # code for method
  end
end

Not only is it much more apparent which actions are supposed to be performed by which objects, but now you won’t have to worry about accidentally calling fly_away on your dog. Who knows what could have happened there!

To build off the benefits of encapsulation, OOP helps keep your code readable. By keeping all of its instance methods tabbed in one level deep, it becomes much simpler to skim through your files.

Requiring Files

To take this organization to the next level, let’s address one last convention when it comes to classes. As a rule of thumb, each class should exist in its own file. This way, when you’re working on an application that has multiple classes, like ClothingStore and Customer, you know exactly where to look to make changes to your code.

These files should have the same name as the class they contain, with a few slight changes. Instead of naming your files in upper camel case (ClothingStore.rb), you should name them using snake case, like you would name a variable (clothing_store.rb).

But how do you get those files to interact with each other? Files don’t just magically know that there are other files that need to be included in your project. You’ll need to explicitly require those files.

Let’s say you’ve got three files: clothing_store.rbcustomer.rb, and app.rb. To include the contents of the first two files in the last file, you can use require_relative. Let’s see that in action.

# app.rb

require_relative "clothing_store.rb"
require_relative "customer.rb"

# The rest of your code...

This code will look for the files you specify relative to the current file. That means that ifcustomer.rb was one directory level above the current directory, you’d writerequire_relative “../customer.rb".

Additionally, Ruby allows you to omit the .rb file extension by default. So your app.rb file can be written instead as:

# app.rb

require_relative "clothing_store"
require_relative "customer"

# The rest of your code...

 

  • Nearly everything in Ruby is an object!
  • You can define your own classes in addition to the standard Ruby classes like Integer to create new types of objects.
  • To see which class a particular object is, you can use the class method.
  • Objects have instance methods, or methods that can be called on a specific instance of a class, like reverse for Strings.
  • To see which instance methods are available for a given object, you can use the methodsmethod.
  • Getter and setter methods can be used to assign and retrieve the values of instance variables.
  • Speaking of, instance variables are scoped to a particular class, and always start with a @.
  • attr_readerattr_writer, and attr_accessor are handy shortcuts for getter and setter methods.
  • Ruby lets you write some things in more convenient ways, like leaving off parentheses. This issyntactic sugar .
  • You can use initialize to take care of any initial set-up for your classes.
  • You can split up your Ruby files using require_relative.
  • Generally speaking, each class should belong in its own file.

 

Instance Variables in ruby

Dogs have many shared characteristics, like the abilities to wag their tails and drink water from a bowl, but they also have information about them that is variable,

like their breed or their name.

Similarly, when designing an application, your users will have common traits, like the ability to log in and out, but some information will be variable,

like a username or email address. Let’s start by setting up a new User class. You can follow along in either irb or by running a script from the command line.

class User
    # stuff will go here
end

By the end of this lesson, you’ll be able to define a User class that can track its own user information.

Say you wanted to assign a user’s username. You might try something like this.

julia = User.new
julia.username = "coolgirl2000"
# NoMethodError: undefined method `username' for #<User:0x007fc6fa034148>

Let’s pause to take a look at that error bit by bit.

  • NoMethodError: If I had to guess, I’d say this likely has something to do with a method not existing.
  • undefined method 'username': Suspicions confirmed. It looks like our code can’t find a ‘username’ method. That makes sense, we never defined one.

But you weren’t trying to create a new method! You were thinking about this username like a variable, right?

Ruby has no way of distinguishing between variables and methods in this case, since they both come after the ., so it only supports instance methods.

What do we do now?! To treat username as a variable, you’re going to need to fake this a bit. Let’s write just enough code to stop this pesky error.

class User
  def username=(value)
  end
end

If you recall, most characters are fair game with methods, including =. In this case, the valueargument stands in place of the value you want to assign to username.

julia.username=("coolgirl2000")
# => "coolgirl2000"

That does the trick… sort of. There’s an awful lot of syntax here, and it’s a bit of a stretch to say this looks anything like the julia.username = “coolgirl2000” we were going for.

Luckily, we can leave off the parentheses since those are optional for methods. These little shortcuts are known as syntactic sugar, since they make writing code with it just a bit sweeter . Let’s see how that looks.

julia.username= "coolgirl2001"
# => "coolgirl2001"

Wow, I already feel so much less stressed looking at that. The fact that the = is pressed up againstusername is still bothering me. I can’t stop looking at it.

It’s going to bother me forever if you don’t do something about it. Please, do something about it.

Syntactic sugar to the rescue again. When a method ends with =, Ruby rightfully assumes that you’re creating a method for variable assignment. To make your life sweeter, Ruby lets you put a space before the = so it reads more like typical variable assignment, like so:

julia.username = "coolgirl2002"
# => "coolgirl2002"

Boom. Check that out. I can sleep easily again. You’ve just successfully created (the start of) asetter method (sometimes called a mutator method).

A setter method is one which sets, or changes, the value of a variable belonging to a particular object.

Alright, let’s check in on your username. If my math is correct, it should be set to “coolgirl2002”right now.

julia.username
# NoMethodError: undefined method `username' for #<User:0x007fc6fa034148>

This error again. You’ve seen this one before, but didn’t you already create this instance method?

Let’s go ahead and write the bare minimum amount of code to make this error go away.

class User
  def username=(value)
  end

  def username
  end
end

This is the opposite of a setter method, a getter method. It exists to get the value of a variable belonging to an object. Let’s see if this works.

julia.username
# => nil

There’s no error , but it’s also returning nil instead of “coolgirl2002”. You may have already caught on to this…

neither your username nor your username= methods actually do anything.

To make your getter and setter methods start working, you’re going to need to capture theusername values somewhere.

The problem here has to do with scope. How can the values you capture in the username= setter method be accessed in the username getter method?

Much like instance methods, you can also use instance variables. Instance variables are variables that are scoped to the entire class.

This means that once an instance variable is defined, it can be used anywhere in that object.

An instance variable looks just like a regular variable, with one minor difference: it starts with @.

class User
  def username=(value)
    @username = value
  end

  def username
    @username
  end
end

Let’s see if this works before moving on.

julia.username = "coolgirl3000"
# => "coolgirl3000"
julia.username
# => "coolgirl3000"

Nice!

The @username instance variable is now available to use by any of the methods inside of a Userobject.

You can go ahead and make other User objects now, and each one will be able to track their own information.

walter = User.new
# => #<User:0x007fc6fa034328>
walter.username = "cooldude17"
# => "cooldude17"
walter.username
# => "cooldude17"

And one last check with Julia to make sure her information is still accurate.

julia.username
# => "coolgirl3000"

Remember when we said that $global_variables weren’t the best solution? This is that best solution.

Very rarely will you need a variable that is available to everything inside your application. Often, you just want it available to one or a few objects.

Let me say that again: You will almost never need to use global variables ever again. If you find yourself using one,

there’s a very good chance it can be done some other way using classes.

Getter and setter methods are used so often in Ruby that there are even more shortcuts you can use to make your life sweeter.

Why type more code than you really need to? Remember, Ruby is optimized for developer happiness.

class User
  attr_reader :username
  attr_writer :username
end

attr_reader and attr_writer (“attr” being shorthand for “attribute”) are two methods that take care of creating getter and setter methods, respectively, for you.

Best of all, you can do this for as many attributes as you’d like. Perhaps you want users to be able to change their usernames, but not their birthdays.

class User
  attr_reader :username, :birthday
  attr_writer :username
end

There’s no need to actually write out all those getter and setter methods yourself anymore!

One thing that confused me at first was the syntax of attr_reader and attr_writer.

It didn’t really look like anything I’d ever seen before, at least not until someone rewrote it for me this way:

attr_reader(:username, :birthday)

These are actually instance methods that are already baked in for you to use.

All you are doing is passing in a Symbol representing the instance variable you’d like to create getter and setter methods for.

Accessors

There’s one last refactor we can do here. It’s pretty common to need both a getter and a setter method for an attribute, so why not define both at once?

That’s exactly what attr_accessordoes.

class User
  attr_accessor :username
end

I know what you’re thinking. Yes, I did just string you along this emotional journey only to end up writing three lines of code.

But think about it: now you know what these three lines of code actually do!

Along the way, you learned about instance variables, some neat syntactic sugar, and getters and setters. Not bad.

Let’s continue with the User example. This is what we’ve got at the moment:

class User
  attr_accessor :username
end

This is useful, but presents a slight issue. We don’t want a user to ever be without a username.

The way things are currently set up, you can easily do something like this:

colt = User.new
# => #<User:0x007fc6fb03ae98>
colt.username
# => nil

Because a User is created without a default username value, Colt is left without a username until he goes in and sets one himself.

This will not do.

There’s a special instance method you can use to make sure an object is properly set up when it’s created, initialize.

When you create a new object, the first thing Ruby will do is look for thatinitialize method and run it.

This makes is super handy for initial setup (for things like… initializing an object with a username, maybe?).

class User
  attr_accessor :username

  def initialize
    @username = "its_colt_outside"
  end
end

Let’s give this another go.

colt = User.new
# => #<User:0x007fc6fb03ae98 @username="its_colt_outside">
colt.username
# => "its_colt_outside"

Beautiful.

There’s just one issue with the previous example— we’re hard-coding “its_colt_outside” to be the default username for every single user.

That’s not going to help you much when Orit wants to make an account for herself.

How about passing in the username as an argument right from the start, like a method, when creating the new User object?

orit = User.new("supermom")

To do this, you’ll need to modify your User class so that it can accept arguments. It may be tempting to do something like this:

class User(username)
  # ...
end

But as you’ll quickly encounter, this will throw an error. This is a completely reasonable assumption, though.

Instead, you can include the argument as a part of the initialize method.

A good way to remember this is to keep in mind that initialize takes care of everything related to the initial set-up of an object.

Since the username argument deals with the initial set-up of a username, it belongs to the initialize method.

class User
  attr_accessor :username

  def initialize(username)
    @username = username
  end
end

Now you can initialize a User object with a username right when you create it.

orit = User.new("supermom")
# => #<User:0x007fc6fb03ae98 @username="supermom">
orit.username
# => "supermom"

This also safeguards you from creating a new user without a username.

mike = User.new
# ArgumentError: wrong number of arguments (0 for 1)

If you break apart that error, it shouldn’t be too tough to figure out what the issue is.

  • ArgumentError: This probably has something to do with the arguments passed into thisUser object.
  • wrong number of arguments (0 for 1): Zero arguments were passed in, when theUser class is expecting one.

Errors can seem scary, but often they contain vital information to help fix bugs in your code. Good error messages will tell you exactly where you need to make a fix.

 

 

compact过滤数组中的nil

http://ruby-doc.org/core-2.2.0/Array.html#method-i-compact

compact → new_aryclick to toggle source

Returns a copy of self with all nil elements removed.

[ "a", nil, "b", nil, "c", nil ].compact
                  #=> [ "a", "b", "c" ]
 render :json => {
      :videos => result_videos.compact.map do |video|
        if video.class == CmsTvIVideo
          if video.video_type == "live"
            {   
              :title => video.title,
              :live_name => video.live_name,
              :live_id=> video.live_id,
              :big_horizontal_image => video.res_img_l,
              :middle_horizontal_image => video.res_img_m,
              :big_vertical_image => video.res_img_s,
              :middle_vertical_image => video.middle_vertical_image,
              :mtype => "live"
            }

invert

http://docs.ruby-lang.org/en/2.0.0/Hash.html

invert → new_hash
Returns a new hash created by using hsh's values as keys, and the keys as values.


h = { "n" => 100, "m" => 100, "y" => 300, "d" => 200, "a" => 0 }
h.invert   #=> {0=>"a", 100=>"m", 200=>"d", 300=>"y"}

 

 <td class="recommend"><%= subchannel_item.if_recommend %></td>
  RECOMMEND = { 
    '否' => false,
    '是' => true
  }
  def if_recommend
    RECOMMEND.invert[recommend]
  end

 

[11] pry(main)> SubchannelItem::RECOMMEND
=> {"否"=>false, "是"=>true}
[12] pry(main)> SubchannelItem::RECOMMEND.invert
=> {false=>"否", true=>"是"}

[15] pry(main)> SubchannelItem::RECOMMEND.invert[SubchannelItem.first.recommend]
=> "是"

ruby学习