Zastępowanie identyfikatora podczas tworzenia w ActiveRecord

104

Czy istnieje sposób na zastąpienie wartości identyfikatora modelu podczas tworzenia? Coś jak:

Post.create(:id => 10, :title => 'Test')

byłby idealny, ale oczywiście nie zadziała.

Codebeef
źródło
1
Wiele z tych odpowiedzi sporadycznie kończy się niepowodzeniem w Rails 4 i mówi, że działają. Zobacz moją odpowiedź dla wyjaśnienia.
Rick Smith

Odpowiedzi:

116

id jest po prostu attr_protected, dlatego nie możesz użyć przypisania masowego, aby go ustawić. Jednak przy ręcznym ustawianiu po prostu działa:

o = SomeObject.new
o.id = 8888
o.save!
o.reload.id # => 8888

Nie jestem pewien, jaka była pierwotna motywacja, ale robię to podczas konwersji modeli ActiveHash do ActiveRecord. ActiveHash pozwala na użycie tej samej semantyki allowed_to w ActiveRecord, ale zamiast migracji i tworzenia tabeli oraz ponoszenia narzutu bazy danych przy każdym wywołaniu, po prostu przechowujesz dane w plikach yml. Klucze obce w bazie danych odwołują się do identyfikatorów w pamięci w pliku yml.

ActiveHash doskonale nadaje się do list wyboru i małych tabel, które zmieniają się rzadko i zmieniają się tylko przez programistów. Więc przechodząc z ActiveHash do ActiveRecord, najłatwiej jest po prostu zachować te same odniesienia do wszystkich kluczy obcych.


źródło
@jkndrkn - nie wiem, co masz na myśli. Mam ActiveRecord::VERSION::STRING == "3.2.11"tutaj (z adapterem sqlite3) i powyższe działa dla mnie.
Felix Rabe
Być może to, czego doświadczyłem, wyraziło się tylko w sterowniku mysql.
jkndrkn
@jkndrkn Działa dla mnie, używając sterownika MySQL dla Railsów 3.2.18
lulalala
pracował dla mnie. uratował mi życie. używany na szynach 4.0.0 / postgres 9.3.5
allenwlee
Zobacz moją odpowiedź, jak to zrobić na Rails 4.
Rick Smith
30

Próbować

a_post = Post.new do |p| 
  p.id = 10
  p.title = 'Test'
  p.save
end

to powinno dać ci to, czego szukasz.

PJ Davis
źródło
2
nie jestem pewien, dlaczego otrzymujesz głosy negatywne, to działa świetnie dla mnie
semanticart
Działa to nadal od wersji activerecord 3.2.11. Odpowiedź wysłana przez Jeffa Deana 2 października 2009 roku już nie działa.
jkndrkn
nie działa, wydaje się działać tylko dlatego, że wywołanie p.save, które prawdopodobnie zwraca fałsz. otrzyma ActiveRecord :: RecordNotFound, jeśli uruchomiłeś p.save!
Alan
29

Możesz też użyć czegoś takiego:

Post.create({:id => 10, :title => 'Test'}, :without_protection => true)

Chociaż, jak stwierdzono w dokumentacji , ominie to zabezpieczenia przydziału masowego.

Samuel Heaney
źródło
Niezłe znalezisko, @Samuel Heaney, mogę sprawdzić, czy działa to doskonale z activerecord 3.2.13.
mkralla11
U mnie też zadziałało; nie było konieczne umieszczanie osobnego pola.
shalott
2
Niestety, to już nie działa na Rails 4, usunęli skrót opcji
Jorge Sampayo
@JorgeSampayo - w Rails 4 nie powinieneś tego potrzebować, ponieważ chronione atrybuty można usunąć na rzecz StrongParams.
PinnyM
21

Dla szyn 4:

Post.create(:title => 'Test').update_column(:id, 10)

Inne odpowiedzi Rails 4 nie działały dla mnie. Wiele z nich pojawiła się do zmian podczas sprawdzania za pomocą konsoli Rails, ale gdy sprawdziłem wartości w bazie danych MySQL, pozostały one na niezmienionym poziomie. Inne odpowiedzi tylko czasami działały.

Przynajmniej w przypadku MySQL przypisanie idponiżej numeru identyfikatora automatycznego zwiększania nie działa, chyba że używasz update_column. Na przykład,

p = Post.create(:title => 'Test')
p.id
=> 20 # 20 was the id the auto increment gave it

p2 = Post.create(:id => 40, :title => 'Test')
p2.id
=> 40 # 40 > the next auto increment id (21) so allow it

p3 = Post.create(:id => 10, :title => 'Test')
p3.id
=> 10 # Go check your database, it may say 41.
# Assigning an id to a number below the next auto generated id will not update the db

Jeśli zmienisz createna new+ save, nadal będziesz mieć ten problem. Ręczna zmiana idpodobnych p.id = 10również powoduje ten problem.

Ogólnie rzecz biorąc, użyłbym update_columndo zmiany, idmimo że kosztuje to dodatkowe zapytanie do bazy danych, ponieważ będzie działać przez cały czas. Jest to błąd, który może nie pojawić się w środowisku programistycznym, ale może dyskretnie uszkodzić produkcyjną bazę danych przez cały czas, gdy mówi, że działa.

Rick Smith
źródło
3
To też był jedyny sposób, w jaki udało mi się go uruchomić w sytuacji w konsoli rails 3.2.22. Odpowiedzi wykorzystujące warianty „zapisz” nie przyniosły efektu.
JosephK
2
Dla szyn 4+ używam:Post.new.update(id: 10, title: 'Test')
Spencer
6

Właściwie okazuje się, że wykonanie następujących czynności:

p = Post.new(:id => 10, :title => 'Test')
p.save(false)
Codebeef
źródło
Chociaż może to działać, wyłącza również wszystkie walidacje, co może nie być zgodne z zamierzeniem pytanego.
Jordan Moncharmont,
Właśnie tego potrzebowałem w przypadku danych początkowych, w których mają znaczenie identyfikatory. Dziękuję Ci.
JD.
Początkowo głosowałem za, myśląc, że to zadziała dla danych początkowych, jak wskazał @JD, ale potem wypróbowałem to z activerecord 3.2.13 i nadal otrzymuję błąd „Nie można przypisać chronionych atrybutów”. Tak więc, głosowano w dół :(
mkralla11
1
Niestety to nie działa w rails 4, otrzymujesz NoMethodError: undefined method `[] 'for false: FalseClass
Jorge Sampayo
@JorgeSampayo To nadal działa, jeśli zdasz validate: false, zamiast po prostu false. Jednak nadal napotykasz problem z atrybutem chronionym - istnieje oddzielny sposób na obejście tego, który przedstawiłem w mojej odpowiedzi.
PinnyM
6

Jak zauważa Jeff, id zachowuje się tak, jakby był attr_protected. Aby temu zapobiec, musisz zastąpić listę domyślnych atrybutów chronionych. Zachowaj ostrożność, robiąc to wszędzie tam, gdzie informacje o atrybutach mogą pochodzić z zewnątrz. Pole id jest domyślnie chronione z jakiegoś powodu.

class Post < ActiveRecord::Base

  private

  def attributes_protected_by_default
    []
  end
end

(Testowane z ActiveRecord 2.3.5)

Nic Benders
źródło
6

możemy zastąpić attribute_protected_by_default

class Example < ActiveRecord::Base
    def self.attributes_protected_by_default
        # default is ["id", "type"]
        ["type"]
    end
end

e = Example.new(:id => 10000)
user510319
źródło
5
Post.create!(:title => "Test") { |t| t.id = 10 }

Nie wydaje mi się to czymś, co normalnie chciałbyś zrobić, ale działa całkiem dobrze, jeśli musisz wypełnić tabelę ustalonym zestawem identyfikatorów (na przykład podczas tworzenia wartości domyślnych za pomocą zadania rake), a ty chcesz zastąpić autoinkrementację (aby za każdym razem, gdy uruchamiasz zadanie, tabela była zapełniana tymi samymi identyfikatorami):

post_types.each_with_index do |post_type|
  PostType.create!(:name => post_type) { |t| t.id = i + 1 }
end
Sean Cameron
źródło
2

Umieść tę funkcję create_with_id na początku pliku seeds.rb, a następnie użyj jej do utworzenia obiektu, w którym wymagane są jawne identyfikatory.

def create_with_id(clazz, params)
obj = clazz.send(:new, params)
obj.id = params[:id]
obj.save!
    obj
end

i używaj go w ten sposób

create_with_id( Foo, {id:1,name:"My Foo",prop:"My other property"})

zamiast używać

Foo.create({id:1,name:"My Foo",prop:"My other property"})

Darren Hicks
źródło
2

W tym przypadku jest podobny problem, który należało nadpisać idjakimś rodzajem niestandardowej daty:

# in app/models/calendar_block_group.rb
class CalendarBlockGroup < ActiveRecord::Base
...
 before_validation :parse_id

 def parse_id
    self.id = self.date.strftime('%d%m%Y')
 end
...
end

I wtedy :

CalendarBlockGroup.create!(:date => Date.today)
# => #<CalendarBlockGroup id: 27072014, date: "2014-07-27", created_at: "2014-07-27 20:41:49", updated_at: "2014-07-27 20:41:49">

Callback działa dobrze.

Powodzenia!.

CristianOrellanaBak
źródło
Musiałem stworzyć idoparty na Uniksie znacznik czasu. Zrobiłem to w środku before_create. Działa w porządku.
WM
0

W przypadku Rails 3 najprostszym sposobem na to jest użycie newz without_protectionudoskonaleniem, a następnie save:

Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save

W przypadku danych początkowych sensowne może być ominięcie weryfikacji, którą można zrobić w ten sposób:

Post.new({:id => 10, :title => 'Test'}, :without_protection => true).save(validate: false)

W rzeczywistości dodaliśmy metodę pomocniczą do ActiveRecord :: Base, która jest deklarowana bezpośrednio przed wykonaniem plików źródłowych:

class ActiveRecord::Base
  def self.seed_create(attributes)
    new(attributes, without_protection: true).save(validate: false)
  end
end

I teraz:

Post.seed_create(:id => 10, :title => 'Test')

W Railsach 4 powinieneś używać StrongParams zamiast chronionych atrybutów. W takim przypadku będziesz mógł po prostu przypisać i zapisać bez przekazywania żadnych flag do new:

Post.new(id: 10, title: 'Test').save      # optionally pass `{validate: false}`
PinnyM
źródło
Nie działa dla mnie bez umieszczania atrybutów w {}odpowiedzi Samuela powyżej (Rails3).
Christopher Oezbek
Odpowiedź @PinnyM Your Rails 4 nie działa dla mnie. idjest nadal 10.
Rick Smith
@RickSmith w podanym przykładzie idzostało przekazane jako 10 - więc dokładnie tak powinno być. Jeśli nie tego się spodziewałeś, czy możesz wyjaśnić to na przykładzie?
PinnyM,
Przepraszam, chciałem powiedzieć, że nadal nie 10. Zobacz moją odpowiedź, aby uzyskać wyjaśnienie.
Rick Smith
@RickSmith - ciekawe, czy ten problem dotyczy tylko MySQL? W każdym razie ogólne użycie bezpośredniego przypisywania kluczy podstawowych dotyczy danych źródłowych. Jeśli tak, to generalnie NIE powinieneś próbować wprowadzać wartości, które są poniżej progu autoinkrementacji lub powinieneś wyłączyć automatyczne zwiększanie dla tego zestawu poleceń.
PinnyM,
0

W Railsach 4.2.1 z Postgresql 9.5.3 Post.create(:id => 10, :title => 'Test')działa, o ile nie ma już wiersza z id = 10.

Nikola
źródło
0

możesz wstawić id przez sql:

  arr = record_line.strip.split(",")
  sql = "insert into records(id, created_at, updated_at, count, type_id, cycle, date) values(#{arr[0]},#{arr[1]},#{arr[2]},#{arr[3]},#{arr[4]},#{arr[5]},#{arr[6]})"
  ActiveRecord::Base.connection.execute sql
wxianfeng
źródło